7 июня 2010 г.

IntellJ IDEA 9 Community Edition

Она меня достала. Я не знаю, как нежно любимая мною компания JetBrains могла выпустить на рынок такое говно. Я не знаю, какие цели они при этом преследовали. Но на мой взгляд, это epic fail. Она дико тормозит, на совершенно скромных по размерам проектах. Она жрет память так, как будто рассчитывает, что я буду втыкать новые планки по мере ее желания. На P4 3.2Ghz, у нее в распоряжении 700Мб памяти -- и проект открывается 10 минут!!! Перерасчет индексов -- 10 минут! Разработчики -- да вы просто охуели!

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

Лучи поноса JetBrains.

P.S. Ах, да. Совсем забыл. Ее папка кэшей занимает 7Гб. Семь гигабайт! Разработчики, да вы что, совсем белены объелись? Чего там хранить на 7Гб??? content.dat.storageData -- 7.5Гб один файл. Неудивительно, что ей нужно 10 минут чтобы хотя бы прочитать его.

30 апреля 2010 г.

ReentrantLock и synchronized(lock)

Пока рылся в исходниках JetLang, наткнулся на такой класс, как RunnableBlockingQueue.java. Мне показалось интересным, что в нем используется ReentrantLock, хотя по функциональности вполне подойдет обычный synchronized() и wait()/notify() -- никакой новой функциональности из ReentrantLock не используется. Стало интересно, есть ли какой-либо эффект в плане производительности?

Взял, прямолинейно переписал RunnableBlockingQueue с использованием plain old synchronized style. Создал свой класс MyPoolFiber, использующий именно эту реализацию очереди, и сравнил производительность на примере пинг-понга.

С дефолтными настройками оба варианта неразличимы по производительности. А вот результат с -server меня удивил -- ReentrantLock весьма заметно быстрее. Примерно 7 к 6 в пользу ReentrantLock. Не ожидал -- мне казалось, что новое concurrency API более мощное, но и более медленное -- в тех случаях, когда используется теми же методами, что и старое.

JetLang

Игрался с библиотекой JetLang. Библиотека довольно компактная, и реализует идею message-based concurrency для джавы. Основной плюс идеи организации взаимодействия потоков через отправку-прием сообщений в том, что, при грамотной реализации, можно обеспечить очень низкий уровень столкновений блокировок (lock contention).

Вообще, я поначалу был несколько разочарован -- ожидал что-то вроде Scala Actors в миниатюре, пусть с ограничениями. Но JetLang это скорее бэкграунд, на фоне которого можно самому реализовать что-то вроде Акторов, если захочется.

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

20 апреля 2010 г.

JFormattedTextField.selectAll

Нужно было сделать, чтобы JFormattedTextField выделял свое содержимое при получении фокуса. Стандартный addFocusListener -> .selectAll не сработал. Оказывается (см Sun bug #4740914) JFormattedTextField переопределяет processFocusEvent и внутри сначала зовет базовую реализацию (которая, в числе прочего, и FocusListener-ы оповещает), а потом переформатирует свое содержимое -- что, разумеется, все выделение напрочь убивает.

Решений нашлось два. Либо уныло-универсальное addFocusListener -> SwingUtilities.invokeLater -> selectAll, либо отнаследовать свой собственный класс от JFTF, и переопределить processFocusEvent, дописав в конец selectAll().

Второй метод мне нравится несколько более, но использовать пришлось первый, потому что создание JFTF в моем случае находится вне моего кода, и заменить JFTF на свою реализацию я не смогу без нерационально большого геммороя.

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

4 апреля 2010 г.

Вышел jdk 1.6 под Snow Leopard

В последнем обновлении наконец-то появилась шестая джава под мак (SL).

Тут же появились и проблемы с ней. java.io.IOException: Keystore was tampered with, or password was incorrect -- вылетает при работе с SSL ссылка

Корень проблем оказался прост -- эппл сменила (скорее всего, просто по ошибке) дефолтный пароль на хранилище сертификатов с changeit на changeme. Если сменить его обратно -- все заработает как встарь. Например, вот так:

sudo -i
cd /System/Library/Frameworks/JavaVM.framework/Home/lib/security/
keytool -storepasswd -keystore cacerts

1 апреля 2010 г.

Кодировка консоли в java

Кодировка консоли в Win не совпадает с системной кодировкой (которая Charset.defaultCharset()). А знать ее часто бывает полезно. Например, запускаемые внешние процессы могут возвращать описания ошибок на русском -- и фиг вы их прочитаете. Конечно, можно просто зашить кодировку Cp866 для русской версии -- но это неправославное решение. А ну как другая локаль будет? Хотелось бы иметь возможность спросить, в какой кодировке работает наша консоль.

Я долгое время был уверен, что такого метода в jdk не предусмотрено. Но, совершенно случайно, раскапывая исходники стандартной библиотеки обнаружил решение для Sun JDK 1.6+ -- приватный статический метод java.io.Console.encoding()

final Class<Console> clazz = Console.class;
final Method method = clazz.getDeclaredMethod( "encoding", new Class[0] );
method.setAccessible( true );
final String encoding = ( String )method.invoke( null );


возвращает на моей системе Cp866, как положено.

23 марта 2010 г.

Scala

Первое впечатление от Scala IDEA plugin: "как же медленно оно компилируется!" Натуральный С++ камбэк. Успеваю заметить штук 5 стадий компиляции. Все-таки javac, пересобирающий проект из 500 файлов за 20 секунд сильно развращает.

18 марта 2010 г.

Зачем synchronized вокруг wait/notify?

Еще один из вопросов, который мучил меня во время знакомства с java threading, и так и остался, в то время, не отвеченным. Зачем сановские инженеры спроектировали wait/notify так, что они обязательно требуют на входе захваченной блокировки? Причем на входе они ее требуют, но внутри себя они ее отпускают -- что-то очень хитрое стояло за таким решением, что-то, что я не мог понять.

Ответ оказался довольно прост. Другое поведение не имеет смысла, потому что не дает возможность реализовать то, для чего нужен wait/notify.

Зачем нужен wait? Точнее -- каков сценарий его использования? Если мы хотим просто приостановить поток -- есть Thread.sleep(). А wait нужен тогда, когда мы ждем какого-то события. Еще точнее, мы ждем выполнения какого-то условия, за которое отвечает какой-то другой поток. Но это означает, что у нашего потока с этим другим потоком есть общее, разделяемое состояние (shared state). Второй поток это состояние меняет, и в какой-то момент оно становится "подходящим" для нас, и мы хотим об этом узнать. И wait/notify это всего лишь инструмент, который дает нам такую возможность. Но если у нас есть разделяемое состояние -- нам просто необходима блокировка в обоих потоках, чтобы избежать проблем с data race и memory visibility. Сами-то по себе методы wait/notify можно организовать без требования блокировки -- но они будут бесполезны

На конкретном примере: вот как выглядит типичный код с wait/notify (представим, что секции synchronized не обязательны)
//общие переменные 
    boolean condition = false;
    final Object event = new Object();
    ...
    //первый поток
    while(!condition){
        event.wait();
    }
    ...
    //второй поток
    condition = true;
    event.notify();

Что помешает второму потоку вклиниться между строчками 7 и 8 -- когда первый уже решил, что он должен ждать, но еще не вызвал метод wait? В этом случае notify() вызванный вторым уйдет в пустоту (пока еще никто ничего не ждет), а wait(), вызванный первым никогда не пробудится -- некому больше будить (забудем пока про внезапные пробуждения). Другой вопрос -- кто гарантирует, что обновленное вторым потоком значение condition=true будет увидено первым? Никаких memory barrier-ов здесь нет, первый поток спокойно может закэшировать condition хоть в регистрах процессора, и быть свято уверенным, что оно все еще false. Еще можно вообразить разнообразные insruction reordering, в ходе которых компилятор может решить переставить condition = true после event.notify(), например.

Ок, договорились: синхронизация необходима. Но зачем так плотно привязывать wait/notify к synchronized? Спроектировали бы wait/notify независимо от синхронизации, и просто указывали бы в recommended practices что правильно писать так-то. Были бы очевидные бенефиты -- сейчас, например, старый-добрый Object.wait/notify работает только со старым-добрым synchronized, а новый Lock.lock()/unlock() только с новым же Condition.await/signal -- а между собой они не работают, что, в общем-то, странно. Казалось бы -- какая разница, каким методом обеспечивать синхронизацию разделяемого состояния между потоками?

Ан нет, без плотного связывания не получится. wait обязан знать многое о мониторе синхронизации, с помощью которого согласовывается разделяемое состояние -- потому что wait должен уметь этот монитор сначала отпустить на входе, а потом захватить заново -- на выходе. Как минимум, wait должен иметь доступ к этому монитору.

Вот мы и приходим к той реализации, что имеем. Сначала нужно захватить монитор какого-нибудь объекта, чтобы обеспечить согласованность состояния condition. Потом можно вызвать wait(), и он должен захваченный монитор отпустить -- иначе как другой поток сможет изменять условие? Но wait нужно вызвать на том же объекте -- иначе как wait() узнает, какой именно монитор из 10 захваченных текущим потоком выше по стеку ему надо освободить? Перед notify нужно захватить тот же самый монитор, чтобы синхронизировать изменения в condition между потоками. Ну и очевидно, что notify() нужно вызывать на том же объекте, чтобы он знал, какой wait ему нужно будить. Такой вот расклад

Чем плохо synchronized(this)?

Несколько лет назад, бродя по исходникам JDK я задался вопросом -- почему там так часто встречается организация блокировки через
private final Object lock = new Object();
....

synchronized(lock){
...
}

хотя для этих целей легко можно использовать synchronized(this)? Я еще понимаю, если нужно несколько мониторов для разных наборов атомарных изменений (хотя, на мой взгляд, это часто первый звоночек что объекту нужна декомпозиция), но я часто видел такой код и когда монитор только один. Какое-то время это было для меня загадкой, пока у кого-то из гуру я не встретил ответа -- использование this как монитора синхронизации может нарушать инкапсуляцию вашего объекта. Монитор синхронизации -- это штука с состоянием: у него есть владелец (owner) который может меняться. Давая возможность клиентам работать с вашим объектом вы не можете запретить им работать с его монитором -- а это может менять его состояние, и нарушать те инварианты, на которые вы рассчитывали, проектируя класс.

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

Но вот вчера у меня, наконец, сложился явный и четкий пример. Итак, код:
public class Runner {

private Thread owner = null;

public synchronized void run( final Callable task ) throws Exception {
    owner = Thread.currentThread();
    try {
        task.call();
    } finally {
        owner = null;
    }
}

public synchronized void check() {
    final boolean invariant = ( owner == null ) || ( owner == Thread.currentThread() );
    if ( !invariant ) {
       //can this code be executed?
       throw new AssertionError( "Lock is broken: " + owner + " is owner, but " + Thread.currentThread() + " is here!" );
    }
}

}

на первый взгляд кажется, что код в строках 17-18 никогда не может быть выполнен. Оба метода синхронизированы, никакой посторонний поток не может влезть, пока текущий поток внутри run(Callable). Однако, сломать этот объект крайне просто:
final Runner runner = new Runner();

final Callable task = new Callable() {
    public Object call() throws Exception {
        runner.wait( 2000 );
        return null;
    }
};

final Thread thread = new Thread( "runner" ) {
    public void run() {
        try {
            runner.run( task );
        } catch ( Exception e ) {
            throw new RuntimeException( e );
        }
    }
};

thread.start();

//ensure thread started
Thread.yield();
Thread.sleep( 100 );
//check the invariant
runner.check();

object.wait() должен вызываться внутри synchronized(object), и, на время ожидания он отпускает монитор. То есть пока поток thread ждет 2 секунды на мониторе runner этот монитор свободен, несмотря на то, что выше по стеку есть synchronized(runner). И в эти 2 секунды любой другой поток может захватить этот монитор -- что мы и делаем, демонстрируя нарушение инварианта класса.

Какой отсюда вывод? Вывод такой: если вы используете callback интерфейсы -- т.е. если ваш код выполняет внутри себя какой-то другой код, пришедший "со стороны" -- вы должны делать это либо вне синхронизации, либо использовать монитор синхронизации, до которого клиентский код не сможет добраться, вроде private final Object lock = new Object()

На данный момент я не вижу других способов (кроме callback), как "открытая" синхронизация с помощью synchronized(this) может нарушить инкапсуляцию. Если класс колбэки не использует -- можно использовать синхронизированные методы спокойно.

17 марта 2010 г.

Чем отличается synchronized метод от synchronized(this) блока?

Аттрибут synchronized -- это флаг на методе. Компилятор не будет вставлять на выходе и выходе из метода инструкции захвата и освобождения монитора, как в случае с synchronized(this) блоком; вместо этого уже JVM, на стадии выполнения кода фиксирует наличие этого флага, и автоматически выполняет требуемый захват и освобождение монитора. То есть все различие -- флаг в описании метода вместо двух байт-кодов в его теле. Возникает вопрос: стоит ли беспокоиться из-за двух инструкций (инструкций байт-кода, не процессора!) Чаще всего -- нет. Но представьте себе, например, что JIT-компилятор использует количество байт-кодов в теле метода как одну из метрик для принятия решения о его инлайнировании? Что, кстати, почти наверняка так и есть...

Вольный перевод отсюда (сноска в конце страницы)

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

Синхронизация

Читаю серию статей от Neil Coffey по синхронизации и вообще многопоточному программированию в java. Неожиданно много вещей, которых я не помню или не знаю. Все-таки вещи, которые мало используешь плохо запоминаются -- я смутно помню, что почти все где-то когда-то встречал, но в общую картину у меня в голове все тонкости так и не собрались -- не было достаточно крупных и сложных задач по многопоточности в моей практике.

В ближайшее время постараюсь уложить все у себя в голове в единую картинку, и написать сюда пару статей. Особенно хочется разобраться с volatile и Thread.interrupt(). Заодно, может быть, пока буду готовить статьи -- и сам, наконец, запомню :)

12 марта 2010 г.

Serialization libraries

Наткнулся на интересный "проект" по сравнению производительности различных способов/библиотек сериализации/десериализации в джава. Comparing varius aspects of Serialization libraries on the JVM platform Мало того, что лишний раз подивился на разницу в скорости стандартной сериализации с Externalizable, так еще и узнал о куче интересных библиотек. Например, Kryo выглядит вполне подходящей заменой стандартному механизму -- быстрее, нагляднее, архитектурно изящнее (механизмы сериализации настраиваются отдельно от сериализуемых объектов -- в отличие от стандартного метода, где объект в любом случае сам задает свой метод сериализации, и он может быть только один). А JSON Marshaller я собираюсь рассмотреть на место XStream в DataGuard -- все равно XStream-ский json даже после тщательной доработки напильником периодически генерирует ересь. Более того, в json marshaller есть и замена для org.json.* -- по их словам она более быстрая и более удобная.

11 марта 2010 г.

Со-процедуры (coroutines)

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

Так вот, сопроцедуры. Это такая штука, когда, наряду с обычным return вводится дополнительный способ завершения функции/процедуры -- обычно, его называют yield. В результате yield выполнение функции не завершается, а приостанавливается. Сохраняется состояние стека, локальные переменные. И в следующий раз, при вызове этой функции выполнение просто продолжится с того же самого места, где в прошлый раз был вызван yield.

Зачем это нужно? Ну во многих случаях это сильно упрощает программу. Например, я знаю веб-фреймворк для джавы , построенный на продолжениях (сontinuations, частный случай), где весь цикл взаимодействия с браузером может быть реализован в прямом смысле циклом -- for/while -- внутри одного метода (точнее, фреймворк написан на джаве, но код веб-приложения под него пишется на javascript/rhino). Когда нужно отправить данные пользователю ваш код просто вызывает что-то вроде var userResponse = postToUser(htmlPage); -- выполнение вашего кода приостанавливается на вызове postToUser, страница отправляется пользователю, он с ней что-то делает, результат отправляется на сервер -- и ваш код его получает в переменную userResponse, продолжая выполнение дальше. Очень изящно, гораздо проще, чем сервлеты.

Другой вариант -- всем известные генераторы, как замена итераторам.

В общем, штука удобная и интересная. К тому же, если верить автору, еще и достаточно быстрая. Если она в самом деле будет включена в jdk1.7 -- будет приятно.


Источник: http://classparser.blogspot.com/ via Levin Matveev blog

Syntax highlighter

Нашел себе подсветку синтаксиса для скриптов в блоге.

В теле поста пишем
<pre class="brush: java">
public static void main( final String[] args){
final int count = args.length;
System.out.println("count: "+count);
}
</pre>




и в результате получаем:
public static void main( final String[] args){
    final int count = args.length;
    System.out.println("count: "+count);
}


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

Подробности здесь: Awesome syntax highlighting

Numbers everyone should know

Numbers everyone (developer) should know (от разработчиков google)

* L1 cache reference 0.5 ns
* Branch mispredict 5 ns
* L2 cache reference 7 ns
* Mutex lock/unlock 100 ns
* Main memory reference 100 ns
* Compress 1K bytes with Zippy 10,000 ns
* Send 2K bytes over 1 Gbps network 20,000 ns
* Read 1 MB sequentially from memory 250,000 ns
* Round trip within same datacenter 500,000 ns
* Disk seek 10,000,000 ns
* Read 1 MB sequentially from network 10,000,000 ns
* Read 1 MB sequentially from disk 30,000,000 ns
* Send packet CA->Netherlands->CA 150,000,000 ns

9 марта 2010 г.

Smack XMPP

На выходных игрался с XMPP. Недавно на хабре был анонс простенькой текстовой игры через jabber: snow@talk2play.ru Мне поднадоело играть в нее самому, захотелось это дело автоматизировать. В итоге, после 3-х дней отладки мой бот более-менее устойчиво набирает очки. Нет ничего приятнее, чем смотреть, как кто-то делает твою работу...

Джабберовский XMPP протокол (и его реализация в Smack) произвел хорошее впечатление. Простой и расширяемый. В будущих проектах собираюсь попробовать предоставлять network interface через него -- на пару к обычному HTTP.

5 марта 2010 г.

Запуск внешних программ

Недавно в DataGuard пришлось разбираться с запуском внешних программ из java. Некоторые вещи оказались довольно нетривиальны, так что я решил поделиться.

На первый взгляд, все достаточно просто -- для простых случаев есть Runtime.exec(), если нужно настроить параметры среды для запуска -- есть ProcessBuilder. В любом случае получаем объект Process, у которого вызываем Process.waitFor(), чтобы дождаться завершения -- и, вроде бы, все?

К сожалению, ничего подобного. Несмотря на то, что API выглядит просто и очевидно, корректное его использование совсем не просто, и не очевидно. Какие конкретно подводные камни нас ждут?

Главный из них -- потоки ввода-вывода (IO streams). У порождаемого процесса нет терминала, к которому он привязан, его stdin, stdout, stderr выдаются порождающему процессу -- то есть, нам. Причем обрабатывать их -- наша обязанность. Потоки, созданные ОС имеют ограниченный размер буфера. Если, к примеру, буфер stdout для запущенного процесса заполнен, со стороны java никто его не читает (==не освобождает) а процесс настойчиво хочет что-то вывести -- то процесс просто окажется заблокирован на IO, и будет ждать, пока stdout кто-нибудь освободит. Если мы не предусмотрели в java код, читающий process.getInputStream() -- получается стандартный дедлок: мы ждем завершения процесса, процесс ждет нас.

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

Конкретный пример: в обычном, штатном режиме работы внешний процесс выдает одну-единственную строчку "Ок" и завершается. Строчка вполне влезает в буфер, поэтому код

final Process p = Runtime.getRuntime().exec( "my-script.bat" );  
final int retCode = p.waitFor();  

работает корректно. Но наступает день Х, когда звезды складываются неудачно. И процесс завершается с ошибкой. И, как и положено уважающей себя программе, старается эту ошибку максимально подробно описать. И пытается вывести в stdout простыню текста, превышающую размер буфера. Вуаля -- процесс ждет на выводе, java-программа -- на process.waitFor()

На мой взгляд -- это пример плохо спроектированного API. Простая вещь -- запустить внешний процесс не заморачиваясь с его выводом -- делается весьма нетривиально. Более того, из самого API это никак не следует. Да, в документации к Process это прописано, но я считаю, что хороший API это такой, использование которого, по крайней мере для простых задач, очевидно без документации. Можно было бы дополнить контракт, например, так: "если клиент не запросил process.getInputStream()/process.getErrorStream() до вызова process.waitFor() -- stdout/stderr внешнего процесса автоматически перенаправляются вникуда".

Но наши друзья из Sun этого не сделали, так что приходится отдуваться самим: ProcessRunner

Что делает: берет сконфигурированный ProcessBuilder, создает внешний процесс, запускает асинхронно "помпы", прокачивающие его потоки ввода-вывода либо в пустоту (если пользователь ничего не задал) либо из/в заранее заданные потоки. Метод ProcessRunner.execute() блокируется пока либо процесс не завершится, либо пока не будет вызван ProcessRunner.interrupt(). Пример использования:
final ProcessBuilder pb = new ProcessBuilder("my-script.bat");  
final ExecutorService pool = Executors.newFixedThreadPool(3); // нужно минимум 3 свободных потока в пуле 
final ProcessRunner pwd = new ProcessRunner( "run", pb,  pool );  

pwd.execute();  

final int retCode = pwd.getReturnCode();  
...
pool.shutdown();


В этом примере ввод-вывод my-script.bat будет просто выброшен. Другой пример:
final ProcessBuilder pb = ...;  
final ProcessRunner pwd = new ProcessRunner( "run", pb, POOL );  

final ByteArrayOutputStream out = new ByteArrayOutputStream();  
final ByteArrayOutputStream err = new ByteArrayOutputStream();  
pwd.setOutputStream( out );  
pwd.setErrorStream( err );  

pwd.execute();  

assertEquals( 0, pwd.getReturnCode() );  
final byte[] output = out.toByteArray();  
final byte[] errors = err.toByteArray();  

Здесь stdout/stderr будут считаны в предоставленные нами потоки. Обратите внимание, что если флаг ProcessBuilder.redirectErrorStream() выставлен в true, то stderr будет слит с stdout, и errors будет пуст.

Больше примеров использования можно посмотреть в тестах ProcessRunnerTest

25 февраля 2010 г.

Data Oriented Programming вместо ООП

Пара интересных статей:

Musings on Data Oriented Design

Pitfalls of Object Oriented Programming (GCAP)

Суть вопроса, который поднимают авторы, такова -- за последние 30 лет развития IT скорость выполнения команд процессором выросла почти в 50000 раз, но время ответа памяти на запрос (memory latency) уменьшилось всего лишь раз в 10. В итоге мы получаем, что "стоимость" запроса к памяти (в циклах процессора) выросла примерно в 400 раз.

При этом современные компиляторы очень хорошо умеют работать с кодом -- фактически, машинный код, получаемый на выходе из компилятора может вообще не иметь ничего общего, с тем, что изначально было написано человеком -- кроме того, что делает то же самое. Но компиляторы очень мало что делают с данными -- размещение данных в памяти (data layout) генерируемое компилятором фактически очень мало отличается от того, что задекларировал программист в коде (разве что компилятор добавит туда от себя что-то -- vtable например, да выровняет по границам машинных слов). Хотя при современном состоянии дел с memory latency было бы гораздо выгоднее оптимизировать расположение данных под выполняемые задачи.

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

Вместо этого предлагается программисту (пока компиляторы не научились) продумывать размещение данных в памяти исходя из того, как они будут обрабатываться. В частности, в презентации (pdf) Том Албрехт показывает, как им удалось в 3-4 раза ускорить обновление-прорисовку дерева объектов сцены (scene tree) за счет изменения компоновки данных (там, правда, еще про предсказание ветвлений упомянуто)

15 февраля 2010 г.

JCaptcha

Разбирался на прошлой неделе с JCaptcha. Хорошая библиотека, но документация -- отвратная. Огромный разрыв между beginners guide и реальным использованием никак не покрыт. То есть начинаешь читать 5 minutes integration -- все вроде просто. Шаг чуть дальше -- нигде кроме как в исходниках инфы не найдешь.

Последний раз так с MFC мучался в дремучих 90-х. Думал уже, в мире джава таких проектов нет...

Вот, для примера, "переведенный" мною с языка spring-configuration на java код инициализации службы генерации капч:

private static final ImageCaptchaService instance;// = new DefaultManageableImageCaptchaService();

private static final int FONT_SIZE_MIN = 25;
private static final int FONT_SIZE_MAX = 29;

private static final int HEIGHT = 40;
private static final int WIDTH = 200;

static {
//Шрифт, которым будет написана капча. Размер не важен
final Font font = new Font( "Times New Roman", Font.PLAIN, 12 );

//разные буквы разными шрифтами разного размера
final FontGenerator fontGen = new RandomFontGenerator( FONT_SIZE_MIN, FONT_SIZE_MAX, new Font[]{ font } );
//...и разными цветами 
final ColorGenerator colorGen = new RandomListColorGenerator( new Color[]{ Color.RED, Color.GREEN, Color.BLUE, Color.BLACK } );
//однотонный белый фон-подложка. здесь, по-сути, задается размер капчи
final BackgroundGenerator bgGen = new UniColorBackgroundGenerator( WIDTH, HEIGHT, Color.WHITE );
//вырезаем слова от 6 до 9 букв
final TextPaster textPaster = new RandomTextPaster( 6, 9, colorGen );
//список слов можно задать 
final FileDictionary dict = new FileDictionary( "toddlist" );
final WordGenerator wordGen = new DictionaryWordGenerator( dict );
//final WordGenerator wordGen = new ComposeDictionaryWordGenerator( dict );

final ComposedWordToImage wordToImage = new ComposedWordToImage( fontGen, bgGen, textPaster );

final CaptchaFactory factory = new GimpyFactory( wordGen, wordToImage );

final GenericCaptchaEngine engine = new GenericCaptchaEngine( new CaptchaFactory[]{ factory } );

instance = new GenericManageableCaptchaService( engine, 300, 500000, 1 );
}

12 февраля 2010 г.

JavaMail + SSL

Продолжение разбирательств. Краткий итог: итак, самый простой и надежный метод работы с SSL -- использовать
System.setProperty("mail.smtp.ssl.enable","true");

Все остальные настройки как в обычном smtp (только порт обычно 465 вместо 25). Работает с gmail -- проверено

Подробное описание:

Вариант с
System.setProperty("mail.transport.protocol", "smtps")

не работает. Точнее, не совсем работает -- не так, как ожидается. Раскопки в исходниках JavaMail (благо, они доступны) показывают, что свойство mail.transport.protocol используется только методом Session.getTransport() (который без аргументов). Но самой библиотекой этот метод не используется -- предполагается, что его вызывает клиентский код явно. Если вы отправляете почту через явно создаваемый транспорт, и получаете его через session.getTransport() -- свойство mail.transport.protocol будем иметь эффект. Если же вы, как я, отправляете почту через статический метод Transport.send(), то внутри него библиотека ищет подходящий транспорт через вызовом getTransport(Address), что отсылает нас к свойству mail.transport.protocol.rfc822 (rfc822 -- такой "тип адреса" у адреса электронной почты). Если хочется переопределить транспорт для такого типа адресов на smpts -- надо писать
System.setProperty("mail.transport.protocol.rfc822", "smtps");
, либо использовать session.setProtocolForAddress("rfc822", "smpts"). Если просто написать mail.transport.protocol = smtps, то Transport.send() это указание проигнорирует.

В числе прочего это означает, что если вы вместе с mail.transport.protocol = smtps укажете, как описано в документации, свойства типа mail.smpts.host -- они будут проигнорированы (использоваться будут свойства типа mail.smtp.host) и JavaMail будет ломиться на localhost (который используется в качестве адреса smtp сервера по-умолчанию).