20 декабря 2009 г.

FBDataGuard в открытом бета-тестировании

FBDataGuard -- это агент мониторинга "здоровья" серверов баз данных под СУБД Firebird. Firebird -- одна из двух (вторая -- PostgreSQL) достаточно известных и широко используемых, и при этом полностью открытых и бесплатных СУБД на рынке.

Основной ее недостаток, как и у большинства open source продуктов -- низкий, по сравнению с коммерческими аналогами, уровень дружелюбности к пользователю. FBDataGuard -- продукт, который пытается как-то справиться с этим недостатком. Это агент, запускаемый в фоновом режиме на том же сервере, что и СУБД, и занимающийся непрерывным мониторингом ряда важных параметров БД, сервера СУБД, и самого хост-компьютера. Начиная от свободного места на дисковых разделах, количества временных файлов, состояния индексов БД, количества активных пользователей, параметров транзакций... Ну и многое другое -- всего списка я сам не помню. Плюс к тому, агент выполняет по расписанию револьверные бэкапы, собирает статистику использования БД (для анализа на предмет оптимизации), хранит актуальную версию метаданных (это в разы облегчает починку БД в случае ее повреждения) и еще всякое-разное. И если что-то ломается, или параметры выходят за заданные границы -- начинает спамить админа письмами с угрозами. Кроме того у агента есть http-based RESTfull API, для получения актуальной информации о текущем состоянии. И -- в качестве примера его использования -- ajax-based веб-страничка мониторинга и управления агентом (веб-консоль). Такая вот красотищща.


В общем, штука незаменимая, непонятно, как люди до сих пор без нее жили. Точнее, в общем-то, понятно -- каждый писал собственные скрипты для всех этих задач. Ну вот мы и решили, что это некошерно, в 21-ом то веке, когда космические корабли...

Продукт бесплатный, возможно даже (пока не решили) ядро будет с открытыми кодами.

Собственно, я один из активных участников процесса разработки. Поэтому, хотя вышеизложенное и является рекламой -- это реклама из первых рук :) И мне за нее не стыдно. А кто хочет присоединиться к бета-тестированию -- добро пожаловать по ссылке в заголовке.

7 декабря 2009 г.

apache wicket

Обнаружил замечательнейший web framework -- apache wicket. Бросил безнадежное дело с написанием нашего проекта на PHP, за выходные переписал все на джаве под wicket, заодно изучая Wicket In Action.

В чем фишка wicket:
1. Шаблоны страниц -- чистый html, только аттрибуты типа wicket:id указывают, где будет привязка к динамическом содержимому. Т.е. шаблон можно верстать в любом редакторе html и просматривать в любом браузере.
2. Динамика для шаблонов -- чистая джава. view+controller вместе, как в swing. Вообще само программирование очень сильно напоминает свинг -- настолько, что поначалу даже непривычно. Постоянно ищешь где же здесь запрятан цикл request-response.
3. Шаблоны можно наследовать и агрегировать. Т.е. можно создавать повторно используемые компоненты, и включать их в другие шаблоны, и можно расширять существующие шаблоны/компоненты. Просто чудо.

Среди прочего, за выходные узнал, что по скорости java servlet container в разы превосходят php. Правда, хотят больше памяти. Так что мои опасения, что джава для фронтенда высоконагруженного веб-приложения не подойдет были не обоснованными.

30 ноября 2009 г.

нужен профайлер

"Преждевременная оптимизация -- корень всех зол". Ну если не всех, то как минимум половины. Вопрос в чем -- что нужно, чтобы эту самую преждевременную оптимизацию не делать?

Заметил -- по мере того, как растет опыт в области оптимизации, как обрастаешь всякими фишками и фишечками на тему "как тут и там немного выиграть в скорости и памяти" начинает возникать все большее искушение вставлять эти фишечки где только можно. В итоге джава, которую я люблю именно за то, что провоцирует писать понятно, превращается в С.

Спросил себя -- что нужно, чтобы искушение оптимизировать все и вся не возникало? Да все просто -- нужна уверенность, что когда проблемы с производительностью возникнут, можно будет четко сказать где. Другими словами -- если я знаю, что у меня есть инструментарий, позволяющий быстро и точно локализовать узкие места -- я буду писать красивый и понятный код. Если я знаю, что профайлер мы уже 3 года как не можем купить -- я оптимизирую все, что можно, на всякий случай.

28 октября 2009 г.

Очередные сложности со столкновениями тел. Краткое содержание предыдущих серий:

Сначала мы столкновения реализовывали упругими силами, расталкивающими тела. Недостатки -- силы должны быть большими, чтобы не допускать глубокого проникновения тел друг в друга, а это приводит к жесткости задачи и довольно большим погрешностям интегрирования. Как правило, даже в не очень сложных задачах энергия флуктуировала на уровне 10-5 что довольно много.

Потом мы реализовали идеальные столкновения. Столкновение 2-х идеальных (недеформируемых) твердых тел даже с учетом вращения можно аналитически точно рассчитать используя только законы сохранения энергии/импульса/момента импульса. На выходе -- идеальный биллиард практически без артефактов -- точность соблюдения законов сохранения достигала 15 знаков после запятой -- фактически, точность машинной арифметики.

Схема отлично работала пока мы не начали ее тестировать в гравитационных полях. Оказывается, если "положить" неупругий шарик на "пол" --плоскость (в поле тяжести), то он начинает (медленно но неуклонно) в нее просачиваться. Причина простая -- на каждом шаге интегрирования шарик приобретает небольшую скорость вертикально вниз, и чуть-чуть смещается тоже вниз. Т.е. к концу шага он оказывается чуть внутри плоскости. Движок collision detection фиксирует взаимопроникновение, движок столкновений отрабатывает физику столкновения, и "отражает" скорость шарика -- теперь он летит вверх. Если столкновение абсолютно упругое, этой скорости точь-в-точь хватает, чтобы к концу следующего шага оказаться там же, где он был в начале предыдущего -- у поверхности пола -- и все начнется с начала. Т.е. шарик будет просто колебаться с периодом 2*dt. Но если столкновение неупругое, то после отражения у шарика будет меньшая скорость, и он потеряет ее в гравитационном поле раньше, чем "выскочит" из взаимопроникновения с плоскостью. И следующий шаг (микропадение-отражение-микроподскок) начнет уже с точки чуть-чуть под поверхностью. Это чуть-чуть будет каждый раз увеличиваться, постепенно шарик будет просачиваться в пол.

Решение было довольно очевидным -- нужно делать коррекцию положения (position correction -- штука, хорошо известная в игровых движках, хоть там и несколько другая физика). Т.е. вместе с модификацией импульсов/моментов тел в ходе столкновения нужно "откатить время" назад, ровно чтобы оказаться в моменте, где объекты еще только-только соприкоснулись. Строго говоря, откатить, конечно, нужно всю систему, но для упрощения жизни мы начали откатывать только пару сталкивающихся тел. Тела перестали проваливаться.

Вместо этого они начали разгоняться. Причина, опять же, легко понятна. Выталкивая шарик к поверхности пола, мы, фактически, совершаем работу против сил гравитации, которые его толкали вниз -- т.е. чуть-чуть увеличиваем энергию системы. Если в системе мала диссипация, эти маленькие работы начинают накапливаться, и система идет в разнос. Починили это завязав коррекцию положения на коэффициент упругости отражения. Казалось бы, настало счастье.

И вот, очередной раунд. Создаем стакан (стенки+пол), поле тяжести, +20 шариков, столкновения неупругие. Через несколько секунд шарики скапливаются на дне стакана. И начинают довольно быстро просачиваться друг в друга и в пол.

Почему? Кратные столкновения. Наше приближение -- при коррекции положения откатывать надо всю систему, но мы откатываем только текущую пару тел -- работает удовлетворительно только если в системе достаточно "свободно", если столкновения только парные, если отматывая время для одной пары тел и убирая их взаимопроникновение мы, тем самым, не создаем взаимопроникновение другой пары тел. В случае стакана с шариками на дне это не так. Каждый шарик в контакте с 2-3-4 другими, а то еще и с полом или стенками. Получается, что для данного шарика коррекция положения будет суперпозицией коррекций для всех его столкновений с соседями. По факту, более-менее правильно пройдет только последняя коррекция, остальные превратятся черт знает во что. Вот это "черт знает" и видно на экране :)

Что будет следующей итерацией пока не знаю. Надо думать. Честно рассчитывать кратные столкновения нереально -- во-первых, только из законов сохранения более чем парное столкновение не рассчитать, во-вторых под это дело придется сильно корежить движок collision detection, в третьих сам алгоритм position correction для этого случая неочевиден.

В раздумьях

16 октября 2009 г.

Зарегистрировал домен и развернул сайт Матконструктора

Поскольку 1С в маркетинге не силен, а завоевывать мир нашему как-то надо, мы (команда, работавшая над его созданием) решили сделать независимый сайт, где и будет происходить все непотребство. А именно, встречайте -- mathkit.ru

Только вы туда пока не ходите -- а ну как там что-нибудь упадет, а мне потом поднимать. Только вчера заработал, пусть хоть поживет немного. Все равно из вас, други, почти никто не знает, что такое этот матконструктор и какая от него в народном хозяйстве польза быть может.

P.S. ...и не думайте, что можно зайти втихоря, и никто не заметит. Я высоко сижу счетчик повесил, всех вас запишут. Приеду -- все узнаю, всех увижу. Вам будет стыдно.

9 сентября 2009 г.

Почему тригонометрия в джаве медленная?

Нашел интересную статью на тему "почему тригонометрия в джаве медленная"



Вкратце -- тригонометрия медленная, потому что точная. Реализация fsin/fcos на процессорах x86 несовершенна -- редукция больших значений аргумента к диапазону [-Pi/4, Pi/4] имеет высокий порядок погрешности, из-за чего для больших значений аргумента результат может быть очень сильно неточным. Эта ошибка тянется с древнейших времен, и теперь уже стала стандартом. Джава же пытается ее исправить, и делает "правильную" редукцию аргумента самостоятельно, перед тем, как вызвать fsin/fcos. Отсюда и дополнительный оверхенд

14 августа 2009 г.

Решил перед уходом в отпуск протестировать "потолок" скорости расчета, на который можно рассчитывать в джаве. Для примера взял простейшую систему из N материальных точек (без столкновений, без вращений), в однородном поле тяжести. Все координаты развернул в линейные массивы double[], и реализовал стандартный Рунге-Кутты 4-го порядка. Итог: real-time (т.е. когда симуляционное время не отстает от реального) считается примерно 4500 объектов (jre1.6.0_14, -server -XX:+DoEscapeAnalysis, P4 3.2Ггц).

Для сравнения: когда у нас еще не было вращения и столкновений, в real-time обсчитывалось примерно 500 объектов. Т.е. я оцениваю так, что потенциал чисто низкоуровневых оптимизаций (развернуть структуры данных, улучшить cache-locality, b т.п.) у нас еще где-то на 300-500% прироста.

В качестве ориентировки переписал (матерясь -- забыл уже все) код на С++, запустил из под Visual Studio. Включил все оптимизации, которые нашел (__inline, глобальную оптимизацию, оптимизацию скорости, SSE2). Результат (примерный) -- процентов на 5-9% быстрее, чем в джаве
Вчера, наконец-то, вчерне закончил работу над вращением. Теперь у нас есть объекты с честными 3-мя вращательными степенями свободы, тензор момента инерции, и уравнения Эйлера для вращательной динамики. Но это еще только треть дела. У нас есть идеальные абсолютно упругие столкновения таких тел, в результате которых они могут приобретать момент импульса. Причем с точным (в пределах машинной точности) сохранением всех задействованных инвариантов -- энергии, импульса и момента импульса.

Считаю себя молодцом. Рассчитать аналитически абсолютно упругое соударение -- когда-то это было бы задачкой для разминки. Сейчас пришлось основательно помучать мозг. В какой-то момент даже подумывал сходить в аптеку за фенотропилом. Но, в итоге, обошелся своими силами. Десяток исписанных листов А4 -- освежил в памяти динамику, кинематику, и немного линейную алгебру.

В планах неупругие столкновения и трение. А пока ухожу в отпуск

7 августа 2009 г.

тригонометрия

Тригонометрические функции в джаве довольно медленные. 1000 проходов по 100000 вычислений синусов на моей машине (P4, 3.2ГГц) выполняется за 8 секунд. Простейшая аппроксимация Тейлора с пятым порядком включительно выполняется в 10 раз быстрее, при этом дает 9-10 верных знаков на интервале (-0.1, 1). И, насколько я помню, аппроксимация Тейлора еще не лучшая для этой цели. Так что если вам нужна быстрая тригонометрия для небольших углов -- добро пожаловать назад в 80-90-е,

5 августа 2009 г.

JStackAlloc

Пока изучал JBullet наткнулся там на интересную вещь. Автор довольно прямолинейно портировал С-шный Bullet, и наткнулся на проблемы с производительностью из-за большого количества создаваемых объектов типа Vector3d, Matrix4d и тому подобных легковесных "value objects" -- в С++ они, понятно, создавались на стеке. В итоге он сделал финт ушами -- создал небольшую библиотечку JStackAlloc, которая эмулирует выделение объектов на стеке для джавы.

В коде мы пишем такую штуку:
public static Vector3f average(Vector3f v1, Vector3f v2, Vector3f out) {
     out.add(v1, v2);
     out.scale(0.5f);
     return out;
}

public static void test() {
     Vector3f v1 = Stack.alloc(Vector3f.class);
     v1.set(0f, 1f, 2f);

     Vector3f v2 = Stack.alloc(v1);
     v2.x = 10f;

     Vector3f avg = average(v1, v2, Stack.alloc(Vector3f.class));
}


* This source code was highlighted with Source Code Highlighter.


после чего над скомпилированным кодом выполняется небольшой ant-task, который, используя ASM модифицирует байт-код класса, превращая его в что-то вроде

public static void test() {
     $Stack stack = $Stack.get();
     stack.pushVector3f();
     try {
         Vector3f v1 = stack.getVector3f();
         v1.set(0f, 1f, 2f);

         Vector3f v2 = stack.getVector3f(v1);
         v2.x = 10f;

         Vector3f avg = average(v1, v2, stack.getVector3f());
     }
     finally {
         stack.popVector3f();
     }
}


* This source code was highlighted with Source Code Highlighter.


Внутри $Stack для каждого потока (ThreadLocal) заводится обычный стек для каждого типа объектов. В общем-то ничего особо нового, просто самые тупые ручные операции (не забыть на каждый alloc сделать push в начале метода, pop в конце, обернуть в finally), кроме того порядком загрязняющие код делаются в автоматическом режиме на стадии компиляции, и в исходном коде не заметны. По словам Джезека (автора) он таким образом сумел получить приемлемую производительность для своей библиотеки.

Интересно это потому, что я, честно говоря, давно уже отказался от мысли делать пулы мелких объектов ради облегчения жизни GC. Еще несколько лет назад, когда был введен generation GC, сановские инженеры писали, что делать такого не следует, мол, аллокация в джаве и так быстрее некуда. По сути, куча в джаве почти всегда "стековая" -- т.е. она растет только вверх, все новые объекты в Эдеме выделяются подряд, один за другим, пока свободное место в Эдеме не закончится -- и тогда запускается GC. Т.е. стоимость аллокации - это, грубо говоря, стоимость изменения указателя на "вершину" кучи. А сборка мусора для короткоживущих объектов очень дешева -- как раз потому, что ее специально оптимизировали под этот случай. А вот если вы используете пулы, то хранимые в них объекты переживают несколько "короткоживущих" поколений, и попадают в "долгоживущее" поколение, что плохо потому, что над ним уже работает "честный" но тормозной сборщик мусора, и чем оно больше -- тем дольше он будет работать. Они меня убедили, и я не использовал пулы для экономии памяти. А вот оказывается, что это может дать какой-то эффект.

С другой стороны, в 1.6.0_14 появилась экспериментальная поддержка escape analysis, который должен разбираться как раз вот с такими вот случаями временных объектов в рамках одного метода. Так что, скорее всего, библиотечка немного запоздала. Ее бы во времена 1.5...

31 июля 2009 г.

Наконец-то раскочегарились работы по физконструктору. Изучаю физику заново -- ощущение, что забыл все. Точнее, саму физику на качественном уровне я еще помню, но вот выписывать уравнения я напрочь разучился. Сижу туплю по полчаса над школьными уравнениями движения. Решение задачи о столкновении шаров заняло часа три и вызвало бурю радости :)

16 июля 2009 г.

Float vs Double

Тестировал производительность плавающей запятой в java. Обычно я везде использую double, но пока изучал jbullet заметил, что там вся арифметика на float-ах, и мне стало интересно.

  1. import junit.framework.TestCase;
  2.  
  3. public class FloatVsDoublePerfomance extends TestCase {
  4.     private static final int[] SIZES = new int[]{ 1000, 10000, 100000, 300000 };
  5.     private static final int ITER = 10000;
  6.  
  7.     public void test() {
  8.         for ( int i = 0; i < SIZES.length; i++ ) {
  9.             final int size = SIZES[i];
  10.  
  11.             _testFloat( size );
  12.             System.gc();
  13.             System.gc();
  14.  
  15.             _testDouble( size );
  16.             System.gc();
  17.             System.gc();
  18.         }
  19.     }
  20.  
  21.     private static void _testFloat( final int size ) {
  22.         final float[] arr = createFloatArray( size );
  23.  
  24.         System.out.printf( "float[%d * %d]\n", size, ITER );
  25.         //warm-up -- cache load, etc
  26.         testFloatCompare( arr );
  27.  
  28.  
  29.         long startedAt = System.currentTimeMillis();
  30.         //compare
  31.         for ( int i = 0; i < ITER; i++ ) {
  32.             testFloatCompare( arr );
  33.         }
  34.         System.out.printf( "\tcompare: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  35.  
  36.         //summ
  37.         startedAt = System.currentTimeMillis();
  38.         for ( int i = 0; i < ITER; i++ ) {
  39.             testFloatSumm( arr );
  40.         }
  41.         System.out.printf( "\tsum: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  42.  
  43.         //mul
  44.         startedAt = System.currentTimeMillis();
  45.         for ( int i = 0; i < ITER; i++ ) {
  46.             testFloatMul( arr );
  47.         }
  48.         System.out.printf( "\tmul: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  49.  
  50.         //div
  51.         startedAt = System.currentTimeMillis();
  52.         for ( int i = 0; i < ITER; i++ ) {
  53.             testFloatDiv( arr );
  54.         }
  55.         System.out.printf( "\tdiv: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  56.     }
  57.  
  58.     private static void _testDouble( final int size ) {
  59.         final double[] arr = createDoubleArray( size );
  60.  
  61.         System.out.printf( "double[%d * %d]\n", size, ITER );
  62.         //warm-up -- cache load, etc
  63.         testDoubleCompare( arr );
  64.  
  65.  
  66.         long startedAt = System.currentTimeMillis();
  67.         //compare
  68.         for ( int i = 0; i < ITER; i++ ) {
  69.             testDoubleCompare( arr );
  70.         }
  71.         System.out.printf( "\tcompare: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  72.  
  73.         //summ
  74.         startedAt = System.currentTimeMillis();
  75.         for ( int i = 0; i < ITER; i++ ) {
  76.             testDoubleSumm( arr );
  77.         }
  78.         System.out.printf( "\tsum: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  79.  
  80.         //mul
  81.         startedAt = System.currentTimeMillis();
  82.         for ( int i = 0; i < ITER; i++ ) {
  83.             testDoubleMul( arr );
  84.         }
  85.         System.out.printf( "\tmul: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  86.  
  87.         //div
  88.         startedAt = System.currentTimeMillis();
  89.         for ( int i = 0; i < ITER; i++ ) {
  90.             testDoubleDiv( arr );
  91.         }
  92.         System.out.printf( "\tdiv: %d ms\n", ( System.currentTimeMillis() - startedAt ) );
  93.     }
  94.  
  95.     private static float[] createFloatArray( final int size ) {
  96.         final float[] arr = new float[size];
  97.         for ( int i = 0; i < arr.length; i++ ) {
  98.             arr[i] = ( float )( Math.random() * 100 );
  99.         }
  100.         return arr;
  101.     }
  102.  
  103.     private static void testFloatDiv( final float[] arr ) {
  104.         for ( int i = 0; i < arr.length; i++ ) {
  105.             arr[i] = arr[i] / arr[i];
  106.         }
  107.     }
  108.  
  109.     private static void testFloatMul( final float[] arr ) {
  110.         for ( int i = 0; i < arr.length; i++ ) {
  111.             arr[i] = arr[i] * arr[i];
  112.         }
  113.     }
  114.  
  115.     private static void testFloatSumm( final float[] arr ) {
  116.         for ( int i = 0; i < arr.length; i++ ) {
  117.             arr[i] = arr[i] + arr[i];
  118.         }
  119.     }
  120.  
  121.     private static void testFloatCompare( final float[] arr ) {
  122.         int _ = 0;
  123.         for ( int i = 0; i < arr.length; i++ ) {
  124.             if ( arr[i] < 50 ) {
  125.                 _++;//просто чтобы компилятор не выкинул сравнение
  126.             }
  127.         }
  128.     }
  129.  
  130.  
  131.     private static double[] createDoubleArray( final int size ) {
  132.         final double[] arr = new double[size];
  133.         for ( int i = 0; i < arr.length; i++ ) {
  134.             arr[i] = Math.random() * 100;
  135.         }
  136.         return arr;
  137.     }
  138.  
  139.     private static void testDoubleDiv( final double[] arr ) {
  140.         for ( int i = 0; i < arr.length; i++ ) {
  141.             arr[i] = arr[i] / arr[i];
  142.         }
  143.     }
  144.  
  145.     private static void testDoubleMul( final double[] arr ) {
  146.         for ( int i = 0; i < arr.length; i++ ) {
  147.             arr[i] = arr[i] * arr[i];
  148.         }
  149.     }
  150.  
  151.     private static void testDoubleSumm( final double[] arr ) {
  152.         for ( int i = 0; i < arr.length; i++ ) {
  153.             arr[i] = arr[i] + arr[i];
  154.         }
  155.     }
  156.  
  157.     private static void testDoubleCompare( final double[] arr ) {
  158.         int _ = 0;
  159.         for ( int i = 0; i < arr.length; i++ ) {
  160.             if ( arr[i] < 50 ) {
  161.                 _++;//просто чтобы компилятор не выкинул сравнение
  162.             }
  163.         }
  164.     }
  165. }
* This source code was highlighted with Source Code Highlighter.



тестировал под 1.6.0_14, с опциями -server -XX:+DoEscapeAnalysis -Xmx300m -Xms300m

В итоге, получается, что сложение/умножение примерно в два раза медленнее для double, деление -- примерно на 30%, сравнение мало отличается. Буду теперь для операций, не требующих особой точности, активно использовать float.

15 июля 2009 г.

Последнее время занят активным изучением физических движков -- на джаве и не только. Основная сложность в том, что задачи, которые решаем мы, нетипичны, ими мало кто занимается.

Например: есть множество движков игровой физики -- часть из них даже бесплатна, и портирована на джаву (JBullet, ODE4j). Но они заточены под максимально быструю симуляцию физики -- т.е. система должна выглядеть похоже на реальную. Основные задачи, которые они решают -- движение большого количества тел в простых силовых полях (чаще всего в равномерном поле тяжести), расчет столкновений, учет связей, диссипация. Самое сложное там -- как раз быстрый расчет столкновений и взаимопроникновений тел, связи и силы реакции. Все остальное делается по остаточному принципу -- как правило используется простейшая интеграционная схема Эйлера, которая, вообще говоря, расходится по энергии, но в присутствии трения становится более-менее стабильной, зато обеспечивает завидную скорость расчета. Демки JBullet, где в real-time просчитываются (и рендерятся!) до полусотни объектов довольно сложной геометрии (на java!) впечатляют. При этом попытка смоделировать идеальный биллиард (без диссипации) на этом движке проваливается -- схема эйлера "взрывается" уже через пару минут симуляции.

С другой стороны находятся чисто научные движки. Но они, как правило, заточены под моделирование какой-то конкретной системы. Т.е. программа с их использованием пишется для решения конкретной задачи или класса задач. Кроме того о real-time речь обычно не идет. Да и на джаву они не портированы -- джава, все-таки, пока еще не особо котируется в high perfomance computing

Нам же нужен движок во-первых универсальный -- т.е. единообразно моделировать большинство задач механики, просто накидал объектов и взаимодействий, и сказал "поехали!" -- и он сам со всем разобрался. Во-вторых, с производительностью, близкой к реальному времени для расчета хотя бы десятка сложных объектов, или до сотни простых (моделировать молекулярную механику -- чем больше, тем лучше). Нужно моделировать всяческие идеализации, используемые в механике: идеальные связи (нерастяжимые невесомые стержни, нити, блоки, цепи), идеальные столкновения. Причем все это должно моделироваться максимально "физически" -- т.е. с как можно лучшим сохранением основных инвариантов уравнений движения (энергии, импульса, момента импульса, и т.п.) на временах моделирования хотя бы порядка 10 минут. Такого пока нигде не видел.

Прикидываю, как буду писать сам.

14 апреля 2009 г.

11 байт

Этап согласования протоколов в моем текущем проекте дал пищу для размышления.

Общая информация: система клиент-серверная,  сервер на джава, клиент нынешний -- на дельфи. Предполагается, что протокол будет открытым, и когда-нибудь информация, предоставляемая через него сервером будет использоваться разными клиентами для разных целей. Система уже существовала в виде версии 1.0, можно считать -- прототипа. Сейчас идет полное переписывание с 0 серверной части, и значительное -- клиентской. 

Сервер мониторит некоторую систему, и, по результатам мониторинга, пишет логи. Логи -- текстовые файлы, роллятся каждый день. Логи эти в дальнейшем доступны через сетевой интерфейс, на их основе делается анализ функционирования мониторимой системы.

Амбула: обсуждаем формат записи в логе. Унаследованный от прототипа формат такой: каждое измерение одной строкой, в строке время, и, через запятую -- числа-результаты. Время -- в формате hh:mm -- 19:30. Например: 19:30, 123564, 5634565, 345645654, 1233

Первый вопрос, который у меня возник: почему только время? Где дата? Оказывается -- дата берется из названия файла (файлы роллятся каждый день, помните? и дата сохраняется в имени файла). Ок, говорю, но ведь так получается, что запись в логе несамодостаточна -- мало иметь саму строчку, надо знать еще, из какого файла она взялась. Давай вместо (или вместе -- чтобы сохранить обратную совместимость) времени будем писать timestamp -- например, в unix-time. Ответ -- "ну это же лишние 11 байт! Да и парсер надо будет переписывать."

На мой взгляд, это хороший пример того, как
  •  Не надо экономить на спичках. Примерный размер лога за день -- 100Кб. Timestamp может нам сэкономить примерно 10% от этого размера -- 10Кб в день. При том, что сервер, предполагается, мониторит промышленную систему с террабайтами данных -- копейки. Но этого мало: если действительно стоит вопрос оптимизации размера (а его на повестке дня не стояло, и не стоит до сих пор. И при проектировании прототипа, когда этот формат файла создавался, такого вопроса тоже не стояло) то простейшее сжатие логов зипом уменьшит их размер раза в 3-4 -- при полном сохранении данных.
  • Форматы и протоколы надо стараться разрабатывать с возможностью расширения. Почему ради добавления одной колонки надо переделывать парсер?
Как можно было решить эту задачу? С минимальными изменениями я вижу такое решение: первой строчкой в каждом файле идут названия колонок. Таким образом, когда мы разбираем файл, мы обращаемся к колонкам не по индексам (которые могут меняться) а по именам. Парсер сразу же становится переиспользуемым -- один и тот же формат файла с разными именами колонок можно использовать в десятках мест. И формат каждого конкретного файла становится расширяемым с сохранением обратной совместимости -- добавление новой колонки будет проигнорировано старым кодом, который про нее ничего не знает, а новый получит все преимущества