Показаны сообщения с ярлыком программирование. Показать все сообщения
Показаны сообщения с ярлыком программирование. Показать все сообщения

вторник, 19 февраля 2008 г.

Оптимизация кода

Сегодня удалось добиться примерно десятикратного ускорения работы моего парсера. Проблема вообще возникла из-за того, что я относился к обработке строк в С/С++ стиле - преобразовывал строку с массив символов и переберал их для нахождения начала и окончания блоков. Делать это на Ruby - далеко не лучшая идея. На языке высокого уровня нет смысла использовать низкоуровневый подход. Если в С при переборе символов строки достаточно инкрементировать значение указателя, то здесь будет создан(а затем и уничтожен) объект для каждого символа. В общем совершенно ничего хорошего. Поэтому использовавние функции String#index с простейшим регекспом даёт значительный эффект. Что конечно же не может не радовать.
Для обзначения начала и окончания блоков используются символы '{' и '}'. Для поиска конца блока использовалась следующая функция


def find_end(str)
arr = str.split(//)
open_count = 0
close_count = 0
pos = 0
arr.each do |x|
open_count += 1 if(x == "{")
close_count += 1 if(x == "}")
if(open_count == close_count)
return pos
end
pos += 1
end
nil
end


Теперь эта функция выглядит так:

def find_end(str)
open_count = 0
close_count = 0
pos = 0
while(pos != nil)
pos = str.index(/\{|\}/, pos)
if(pos != nil)
open_count += 1 if str[pos] == 123
close_count += 1 if str[pos] == 125
end
if(open_count == close_count)
return pos
end
pos += 1
end
nil
end


Вероятно этот код можно ещё ускорить, но я пока не знаю как. Кроме ускорения за счёт оптимального использования возможностей языка, значительного эффекта можно добиться за счёт применения более эффективного алгоритма. Здесь у меня тоже есть над чем поработать. =)

пятница, 15 февраля 2008 г.

Проблемы с производительностью

В проекте, над которым я сейчас работаю, достаточно активно используются файлы в xml-подобном формате. И я решил написать для него парсер на Ruby. Сделать это оказалось достаточно просто, но вылезла другая проблема - проблема производительности. Файл в 650 строк на моём стариньком ноутбуке (500 МГц P3) парсится примерно 20 секунд. В Ruby есть очень полезаная библиотека profiler, позволяющая посмотреть сколько именно времени занимает каждая из частей программы. Причём для того, чтобы её применить не нужно ничего менять в своём коде, а достаточно только подключить профайлер. Так вот использование профайлера показало мне, что больше всего работы происходит внутри Array#each. Сейчас думаю, каким образом можно всё ускорить. Возможность сделать всё быстро доказывается наличием парсера rexml, полностью написаного на Ruby.

четверг, 17 января 2008 г.

Реальное применение

Вчера на работе понадобилось преобразовать файл из SVG-формата, в наш собственный. И для этого я решил написать скрипт. Вначале попробовал это сделать на Tcl, но у него таки несколько грамоздкий синтаксис, когда нужно заниматься поиском подстрок и тому подобными вещами(допускаю, что я просто недостаточно хорошо разобрался). Поэтому я использовал Ruby. Здесь проблем вообще не возникло. Я использовал то, что уже делал раньше и всё легко получилось. Так что похоже это и есть первый случай реального применения мной Ruby.

понедельник, 14 января 2008 г.

А теперь на Ruby

Сегодня я реализовал на Ruby ту же программу для копирования с пропусками, что ранее сделал на Tcl. На Ruby получилось следующее:



require 'find'
require 'fileutils'
#счётчики количества файлов и папок
file_count = 0
dir_count = 0

if(ARGV.size != 2)
puts "Script usage:"
puts "ruby no_svn_copy.rb source destination"
end

#получаем и нормализуем пути
src = File.expand_path(ARGV[0])
dst = File.expand_path(ARGV[1])
puts src + ' => ' + dst

#обрабатываем всё что есть в директории src
Find.find(src) do |path|
if(path.scan(".svn").size != 0)
next
end
#формируем путь к цели
filename = dst + path.gsub(src, '')

puts path
#если это директория, то создаём её в новом месте
if (FileTest.directory?(path))
dir_count += 1
FileUtils.mkdir_p filename
end
#если файл, то копируем его
if (FileTest.file?(path))
file_count += 1
FileUtils.cp path, filename
end
end
#выводим статистику
puts "dirs: " + dir_count.to_s +
" files: " + file_count.to_s



Основное отличие программы на Ruby от Tcl заключается в том, что я использовал возможности модуля Find, что позволило сразу получить список всех исходных файлов и папок, а затем обойти его в цикле, а не делать это самостоятельно с помощью рекурсии. Время выполнения в обоих случаях примерно одинаковое.

понедельник, 24 декабря 2007 г.

Совершенный код

Вчера наконец-то доставили заказаную мной две недели назад книгу Стива Макконнелла Совершенный код. Практическое руководство по разработке программного обеспечения. Это более 850 страниц в твёрдой обложке. Судя по отзывам, эту книгу определённо стоит прочитать(и соответственно стоило заказывать =) ). Как только закончу читать Александреску Современное проектирование на С++, примусь за Макконнелла.

вторник, 11 декабря 2007 г.

Интеграция Tcl в программу на C/C++

Книжку по Tcl, про которою я говорил ранее, я дочитал. В ней есть глава, посвяшённая теме моего поста. Использование Tcl оказывается весьма и весьма простым.
Есть три задачи, которые необходимо решать при работе с любым скриптовым языком:


  1. Вызов из основной программы скриптовых функций.

  2. Вызов из под скрипта функций основной программы.

  3. Передача данных между скриптом и основной программой.

В принципе третий пункт вполне можно реализовать через первые два.
В следующем примере создаётся инстанс интерпретатора, создаётся Tcl функция equal, которая реализуется на C и вызывается в интерпретаторе.

#include "tcl.h"

//Хэндл интерпретатора
Tcl_Interp* interp = NULL;

//функция, которая будет вызвана интерпретатором
int EqualCmd(ClientData clientData,
Tcl_Interp* interp, int argc, char** argv)
{
//устанавливаем возвращаемое значение
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(strcmp(argv[1], argv[2]) ? 0 : 1 ) );
return TCL_OK;
}

int main(int argc,char** argv)
{
//создаётся инстанс интерпретатора
interp = Tcl_CreateInterp();
int bResult;
//создаётся Tcl функция equal, которая обрабатывается в ф-ции EqualCmd
Tcl_CreateCommand(interp, "equal", (Tcl_CmdProc*)EqualCmd,
(ClientData*)NULL, (Tcl_CmdDeleteProc*)NULL);
//вызываем функцию equal для значений 10 и 10
if(TCL_OK == Tcl_Eval(interp, "equal 10 10"))
{
Tcl_Obj* pResult = Tcl_GetObjResult(interp);
Tcl_GetBooleanFromObj(interp, pResult, &bResult);
printf("result = %i\n", bResult);
}
//вызываем функцию equal для значений 20 и 10
if(TCL_OK == Tcl_Eval(interp, "equal 20 10"))
{
Tcl_Obj* pResult = Tcl_GetObjResult(interp);
Tcl_GetBooleanFromObj(interp, pResult, &bResult);
printf("result = %i\n", bResult);
}

//удалякм инстанс интерпретатора
Tcl_DeleteInterp(interp);
return 0;
}


Для обмена данными между интерпретатором и программой используются функции Tcl_SetVar и Tcl_GetVar, которые позволяют устанавливать и получать значение переменных. Кроме этих вполне ожидаемых средств в Tcl есть и другая интересная фишка - возможность линковать между собой переменную в C коде и переменную в Tcl скрипте. Когда меняется одна, меняется и другая.

воскресенье, 2 декабря 2007 г.

Парсинг XML

Продолжаю экспериментировать с Ruby и Tcl. И я решил написать на каждом из этих языков парсер xhtml-документов. Задача - преобразовать все xhtml-документы в текущей директории в текстовые документы. Преобразование самое простое - в сущности оно заключается просто в удалении тэгов из документа. На обоих языках задача была выполнена.

Програма на Tcl выглядит следующим образом


package require xml

set skip_data false
set result ""

#колбэк для обработки текста xml-элемента
proc cdata {data args} {
global skip_data
global result
if {$skip_data == false } {
append result $data

}
set skip_data false
}

#колбэк для обработки начала xml-элемента.
#если встречаются тэги <style> или <title>, то мы их пропускаем
proc elem {data attlist args} {
global skip_data
if {$data == "style"} {
set skip_data true
} elseif {$data == "title"} {
set skip_data true
}
}

set l [eval glob *.html]
set b [split $l]
# b - список html-файлов в текущей директории

foreach fn $b {
#считываем содержимое файла
set f [eval open $fn]
set data [eval read $f]
close $f
#создаём парсер и указываем колбэки
set parser [::xml::parser -characterdatacommand cdata -elementstartcommand elem]
$parser parse $data
set result [string trim $result]
set ofn $fn
append ofn {.txt}
#записываем результат в файл
set res_file [open $ofn w]
puts $res_file $result
close $res_file
}

На Ruby получился следующий код

require 'rexml/parsers/PullParser'

$result = ""
#получаем список html-фалов
h_files = Dir.glob("*.html")
#обрабатываем в цикле все эти файлы
for fn in h_files
#считываем данные
f = File.open(fn, "r")
if(f.eof)
p "empty"
return
end
res = f.read
#создаём парсер
lp = REXML::Parsers::PullParser.new(res)
skip_element = 0
while(lp.has_next?)
data = lp.pull
if(skip_element > 0)
skip_element -= 1
next
end
#если попадается ненужный элемент, то пропускаем его
if(data.start_element? && (data[0] == "style" data[0] == "title"))
skip_element = 2
end
if(data.text?)
#а если нужный, то сохраняем его
$result = $result + data[0].to_s
end
end
#записываем результат в файл
res = File.open(fn.to_s + ".txt", "w")
res.write($result.strip)
end


Несмотря на то, что текст програмы на Ruby получился лаконичнее, чем на Tcl, на Ruby вариант у меня ушло значительно больше времени, большую часть из которого я потратил на то, чтобы найти и использовать подходящий xml-парсер. Кроме того результат, который выдаёт Ruby, некорректен - в тексте сохранились значки "&nbsp;". Хотя конечно это недостаток парсера, а не языка... Тем не менее Tcl в данном случае показал себя гораздо лучше. Правда при этом програма на Ruby работает примерно в два раза быстрее.

Сортировка на Ruby

А теперь тоже самое, но на Ruby.

a = [2, 1, 26, 14]
for i in (0..a.length - 1)
      for j in (i..a.length - 1)
            if a[i] > a[j]
                  tmp = [i]
                  a[i] = a[j]
                  a[j] = tmp
            end
      end
end
p a

Естественно в Ruby присутствуют и встроенные средства сортировки массивов
data = data.sort


На написание этой программы на Ruby мне потребовалось меньше времени и услилий, чем на Tcl за счёт более удобного и привычного доступа к элементам массивов в Ruby.

Пузырьковая сортировка на Tcl

Только что написал на Tcl сортироку пузырьком. Выглядит это следующим образом

set a {23 4 3 7 6 10}
puts $a
for { set i 0 } { $i < [llength $a] } { incr i } {
      for { set j $i } { $j < [llength $a] } { incr j } {
            if { [lindex $a $i] > [lindex $a $j]} {
            set tmp [lindex $a $i]
            lset a $i [lindex $a $j]
            lset a $j $tmp
           }
     }
}
puts $a

Естественно того же эффекта можно добиться одной строчкой с
использованием средств языка.
set a [lsort -integer $a]

Просто хотелось написать на этом языке что-нибудь простое. Хотя конечно Tcl предназначен для решения совершенно других задач.

пятница, 30 ноября 2007 г.

Tcl

Не так давно мне в книжном магазине попалась книга Азбука Tcl за какие-то смешные деньги. Ну я не удержался и купил её. Программиста-прагматика я уже дочитал и решил приняться за эту книгу. Никаких откровений там нет, зато есть описание основных возможностей языка. В целом довольно любопытно. Отличительных особенностей я пока заметил две - интеграция с GUI библиотекой Tk и ориентированность языка на работу со строками и списками.

понедельник, 26 ноября 2007 г.

Visual Studio и многоядерность

До сегодняшнего дня я думал, что Visual Studio фактически не использует возможности, предоставляемые многоядерностью(многопроцессорностью). По умолчанию при наличии нескольких ядер распаралеливание компиляции происходит только по проектам. А ведь уже давно тот же IncrediBuild умеет распаралеливать компиляцию по отдельным файлам. И вот сегодня я узнал, что у майкрософтовского компилера есть волшебный ключик /MP. После его добавления в настройки проекта магическим образом компиляция стала занимать не 61, а 38 секунд. А это 60% выигрыш.

среда, 21 ноября 2007 г.

Microsoft Visual Studio 2008

Майкрософт таки разродился новой версией Visual Studio. Сегодня будем качать Express версию, а завтра посмотрим, чего там есть нового и хорошего... И сколько исходников в проекте придётся в связи с этим исправлять =)

понедельник, 12 ноября 2007 г.

Ruby

Недавно я решил заняться изучением ещё одного языка программирования. Среди подходящих вариантов я рассматривал D, Perl, Ruby и Python. От D я отказался в силу того, что этот язык по своим возможностям не слишком превосходит(как мне показалось) гораздо более распространённые C# и Java. Кроме того, хотелось изучить скриптовый язык с динамическкой типизацией. В конечном итоге я остановился на Ruby. Уже написал свой "HelloWorld!", простенький парсер конфигов и программку, загружиющую из интернета данные с использованием прокси. =)

суббота, 10 ноября 2007 г.

Школьные задачки

Купил сегодня сборник задач по программированию за 170р. Всего в нём 1600 вопросов разного уровня(по крайней мере так написано на обложке =) ). Классная вещь! Я смогу сэкономить кучу времени при подготовке к урокам. Задачи охватывают большое количество тем и могут быть использованы вне зависимости от используемого языка. Хотя я качестве языка преподавания выбрал C, в котором нет булевого типа, что накладывает свой отпечаток.
Книга состоит из 16 глав, каждая из которых содержит в себе задачи и вопросы, относящиеся к определённому разделу( например Условный оператор, Строки символов, Одномерные массивы, Случайные числа).
Большая часть задач достаточно простые и не предполагают сложных решений. Но есть и задачи повышенной сложности. Первая задача в теме Рекурсия требует написания рекурсивной функции вычисления факториала(кстати именно её я демонстрировал, когда объяснял эту тему ). В одной из задач повышенной сложности требуется написать рекурсивную функций, проверяющую является ли число простым. Аналогичным образом организованы и другие главы.

вторник, 9 октября 2007 г.

Консольная кодировка

Был тут давеча весьма шокирован. Совершенно неожиданно для меня оказалось, что под виндой текст в консоль выводится в досовской кодировке. Больше всего меня удивляет, как я мог не замечать этого раньше. Ведь уже далеко не первый год программированием занимаюсь. А обнаружил это только когда начал школьникам преподавать курс программирования на C. Похоже, что раньше я просто ничего не пытался выводить в консоль кириллицей. Причём с MSVC всё легко решается с помощью
setlocale(LC_CTYPE, ".1251");
то при использовании LCC-Win32 или MINGW это почему-то не помагает. Так пока и не удалось забороть проблему. Буду дальше бороться.
Кстати, я сразу же решил попробовать вывести строчку кириллицей с помощью C#. Там никаких проблем не возникло. Слава Юникоду! =)

Использование инлайнов.

Сегодня возникла с коллегой дискуссия по поводу использования inline-функций членов классов. Он старается пихать инлайны практически везде, где возможно. Есть даже виртуальные инлайн-функции. Тем не менее в современных версиях Microsoft Visual Studio( начиная с 2003) это ключевое слово фактически потеряло актуальность, ибо оптимизатор сам решает, делать ли функцию inline или же нет. Я склоняюсь к тому, что во многих случаях попытка сделать функцию инлайновой является преждевременной оптимизацией.