Scala, Java, JVM и другое [entries|archive|friends|userinfo]
Scala, Java, JVM и другое

[ website | Об этом блоге ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Kotlin в ЕКб [May. 23rd, 2012|06:20 pm]

stepancheg
[Tags|]

В пятницу буду в Екатеринбурге рассказывать про Kotlin. Есть кто-нибудь из Екатеринбурга? Пишите, пообщаемся.
Link5 comments|Leave a comment

maven-copy-plugin [Apr. 9th, 2012|04:00 am]

stepancheg
[Tags|]

Most build scripts or applications operate with lots and lots of archives: big, small, huge, "*.zip", and "*.tar.gz". Their content comes from various resources: files, directories, Maven dependencies and other archives, sometimes downloaded from HTTP or FTP. The resulting archives may need to become Maven artifacts and occasionally uploaded back to FTP or SCP.

Traditionally, Maven's way to deal with this kind of tasks was lengthy and unclear:

maven-resources-plugin allows to copy resources
maven-dependency-plugin allows to copy and unpack dependencies
truezip-maven-plugin allows to unpack and pack archives
maven-assembly-plugin also allows to pack archives
build-helper-maven-plugin allows to attach created archives as Maven artifacts
maven-antrun-plugin provides required networking support for FTP and SCP, also used for archiving needs

Eventually, when one needs to perform above operations, the result is usually a messy POM with quite a few plugin configurations and Ant snippets. "maven-copy-plugin" solves this issue and provides an elegant solution for all above tasks. It allows you to easily perform and configure the following operations:

* Copy files, directories and Maven dependencies.
* Filter and replace text files as they're copied.
* Pack, update, and unpack archives, zip entries and Maven .
* Attach archives created as Maven artifacts or deploy them directly to Maven repository manager.
* Download and upload archives from and to HTTP, SCP, and FTP.
* Use Groovy "extension points" for text replaces, files filtering and post-processing.

Ад-ад-ад!

оттуда

Однажды Maven развалится по своей тяжестью. Надеюсь, это произойдёт скоро.
LinkLeave a comment

else [Apr. 9th, 2012|03:03 am]

stepancheg
[Tags|]

Очень тяжело после 10 лет программирования ставить else на следующей строке после закрывающей фигурной скобки (такие в нашем проекте требования по форматированию кода).
Link4 comments|Leave a comment

Oracle нам обещает [Mar. 31st, 2012|02:46 am]

stepancheg
[Tags|, ]

На сайте конференции QCon после выступления кого-то из Oracle выложили PDF. Нам обещают:

2013 год, Java SE 8:

— per-threadgroup resource tracking/management. Очень нужно.
— improved sharing between JVMs in same OS. Не знаю, что это, но, наверное, что-то очень полезное.

2015 год, Java SE 9:

— self-tuning JVM. Скорее бы, а то надоело уже параметры вручную настраивать.
— large arrays. Large arrays — это большой overhead на хранение индексов, их делать не надо, а надо сделать BigByteBuffer.

2017 год, Java SE 10:

— reified generics. Некоторая польза от них есть, хотя я больших преимуществ не вижу.
— structs, unified type system. Обязательно.

6 лет. Или султан сдохнет, или ишак, или Ходжа.
Link7 comments|Leave a comment

KDoc и Maven [Mar. 24th, 2012|05:04 am]

stepancheg
[Tags|, , ]

В проекте Kotlin есть KDoc — типа javadoc, но только для Kotlin. Выглядит очень симпатично, примерно так. Написан на Kotlin кстати.

Раньше для генерации kdoc использовался шелл-скрипт, который запускал Ant. Всё было просто и понятно — запустил — работает. Сейчас мне понадобилось снова сгенерировать, и я не нашёл, как! Вижу файлы pom.xml. Запускаю mvn. Он сначала год что-то скачивает из интернетов, потом на страницу описывает в каком порядке reactor plugin всё это будет собирать, потом пишет, сколько ему для этого понадобилось памяти. Где-то в конце для особо тупых ссылка на Wiki.

Пробую дальше: mvn help, mvn --help, mvn doc, mvn kdoc, mvn generate, ничего не помогает.

Пришлось писать письмо автору. Оказывается, надо было запустить mvn clean install. Я не понимаю, какую логичную последовательность действий я должен был совершить, чтобы это узнать самостоятельно?

facepalm.jpg

Кстати, чтобы удалить папку target, maven скачивает Maven Clean Plugin.
Link4 comments|Leave a comment

Inspection [Mar. 20th, 2012|09:26 pm]

stepancheg
[Tags|, , ]

Idea выдаёт warning на абсолютно корректный фрагмент кода:

new PrintStream(System.out);

(надо закрыть PrintWriter, говорит), но на фрагмент кода с очевидной ошибкой говорит, что всё хорошо:

PrintStream ps = new PrintStream(new FileOutputStream("a.txt"));
try {
} finally {
    ps.close();
}


Вот я думаю, может быть хватит уже заниматься перфекционизмом? Смириться с несовершенством мира?

Каждый день отключаю по два дающих ложных срабатывания инспекшна и интеншна в Idea, а они никак не заканчиваются.
Link13 comments|Leave a comment

queues и их поддержка в IDE [Mar. 18th, 2012|02:46 am]

stepancheg
[Tags|, , , , , , ]

Idea не поддерживает ни MQ, ни stgit.

Главное, зачем нужна поддержка MQ/stgit — это чтобы синеньким и зелёненьким показвывалась не только разница между рабочей копией и последним комитом, а чтобы можно было переключаться на показ разницы между рабочей копией и предыдущим комитом (т. е. содержание текущего патча, которое в Idea сейчас смотреть неудобно). Пройдите по ссылкам и нажмите на кнопку vote, если вам близка эта проблема.

То, что я дальше описываю, уже совсем мои фантазии, потому что маловероятно, что эти фичи в ближайшие годы сделают в Idea или в Eclipse. Так что в багтрекер даже не пишу, чтобы не засорять эфир.

Первая фича относительно проста в реализации. Я смотрю на исходник, и мне синеньким и зелёненьким показывается разница между рабочей копией и предыдущим патчем (при наличии поддержки MQ/stgit). Часто хочется чуть-чуть порефакторить перед тем, как делать текущую задачу. Культура использования VCS требует комитить рефакторинги отдельными комитами для удобства чтения комитов другими разработчиками, для более лёгкого мёрджа, для более простого отката и т. п. Сейчас добросовестный разработчике вынужден делать так: stg refresh; stg pop; stg new refactoring; ...; stg refresh; stg push. Утомительно. Хочется, чтобы можно было кликнуть правой кнопкой мыши на синенькую или зелёненькую полоску слева, и выбрать в контекстном меню один из пунктов: "перенести это изменение в патч (выбор из списка)" и "создать предыдущий (или следующий) патч и перенести изменение в него".

Вторая фича нереально сложна в реализации, но жизненно необходима. Я поправил код в классе Foo. Потом я понимаю, что сначала надо чуть порефакторить код, вместо класса Foo сделать интерфейс, а сам класс переименовать в FooImpl. Я создаю предыдущий патч "refactoring", делаю в нём рефакторинг, потом делаю stg push (или hg qpush), а патч не применяется! Потому что в патче написано, что он изменяет строки в файле Foo.java, в этом файле уже совсем другое, а нужные строки находятся в файле FooImpl.java. Вот хочется, чтобы IDE умела делать рефакторинг патчей. Если я переименовываю класс, то класс должен дополнительно переименоваться во всех патчах, которые находятся ниже по стеку. Аналогично с change signature, extract parameter и я не знаю ещё миллион всяких рефакторингов. А то иногда приходится sed на файлы патчей применять, чтобы вручную конфликты не разрешать. Такая фича, кстати, поможет против переименований, замомиченных коллегами.

Про культуру работы с VCS я уже писал раньше. Тут, мне кажется, есть проблема курицы и яйца. IDE (и Idea, и Eclipse) на оч. примитивном уровне работает с DVCS. Разработчики не пользуются никакими продвинутыми фичами DVCS. Авторы IDE не делают поддержку сложных сценариев работы с VCS потому, что нет спроса. Разработчики не используют сложных сценариев работы с DVCS потому, что в консоли они работать не умеют, а их любимая IDE эти сценарии не поддерживает.
Link10 comments|Leave a comment

Collections.emptyList() [Mar. 16th, 2012|12:32 am]

stepancheg
[Tags|, , ]

Часть 1. invokevirtual



Я уже писал, что invokeinterface — страшная операция, сейчас хочу развить эту тему.

Оптимизатор работает так:

Если тип объекта точно известен, то оптимизатор заменяет вызов виртуального метода на обычный goto, и даже инлайнит, и всё хорошо.

Если тип объекта неизвестен, но в 99,99% случаев оказывается объектом одного типа, оптимизатор генерирует примерно такой код:

if (obj.getClass() == ConcreteType.class) {
    invokespecial obj.foo(params);
} else {
    invokevirtual/invokeinterface obj.foo(params);
}


Предсказатель ветвлений процессора успешно предсказывает первую проверку, и вызов выполняется как обычный goto, и даже инлайнится, и всё хорошо.

Но если объект каждый раз разный, то оптимизатор ничего уже поделать не может, и вынужден делать invokevirtual или даже invokeinterface.

Часть 2. Не используйте Collections.emptyList()



Что это значит на практике.

В JDK есть утилита Collections.emptyList(). Несть числа разработчиков пишут такой код:

if (condition) {
    return Collections.emptyList();
} else {
   List r = new ArrayList();
   // fill r
   return r;
}


Вот это как раз этот случай. Замена первого return на new ArrayList(0) приводит к существенному ускорению работы с коллекциями, но появляется небольшая потеря скорости на GC.

Я написал небольшой тест для иллюстрации этих утверждений. Тест сначала генерирует случайное число от 0 до 2, если выпадает на 0, создаёт пустую коллекцию различными способами, иначе создаёт ArrayList и заполняет его числами. Список возвращается из метода, по нему делается проход с помощью обычного foreach.

По ссылке четыре варианта теста:

— intsArrayList всегда создаёт ArrayList
— intsEmptyList создаёт Collections.emptyList() для пустого списка
— intsArrayListCached всегда возвращает ArrayList, но для пустого списка использует глобальную переменную, чтобы не тратить время на выделение пустого ArrayList
— intsArrayListUnmodifiable всегда возвращает ArrayList, завёрнутый в Collections.unmodifiableList(), для пустого списка используется закешированная копия

Результаты такие (меньше — лучше):

empty_list: 2632
array_list: 2216
arl_cached: 2011
unmodifiab: 2844

Использование emptyList считаем за baseline.

Выделение нового ArrayList даёт 10-20% прироста производительности (но надо помнить, что не учитываются накладные расходы на сам тест, т. е. чистый прирост ещё больше, и что не учитывается работа GC, которая КМК, создаёт небольшую нагрузку)

Использование закешированного ArrayList даёт ещё 5-10% производительности.

Использование ArrayList+unmodifiableList существенно проигрывает всем вариантам. Почему это происходит, понятно не до конца. То ли из-за indirect memory access, то ли оптимизатор не догадывается, что работа происходит всегда с одним и тем же типов, внутри которого лежит один и тот же тип, может быть итерирование слишком дорогое в unmodifiableList, может быть дело в излишней нагрузке на GC. Скорее всего замедление происходит по совокупности этих причин.

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

Практические выводы.

Вариант с ArrayList+unmodifiableList использовать не надо.

Использование shared пустого ArrayList кажется мне слишком опасным: если возвращать из метода этот общий пустой объект, какой-нибудь стажёр в него что-нибудь запишет, потом придётся год отлаживать, чтобы найти проблему.

Я думаю, можно всегда возвращать новый ArrayList.

Часть 3. Используйте ReadOnlyArrayList



Правильное решение — это использование класса ReadOnlyArrayList.

Схематично класс ReadOnlyArrayList выглядит так:

public class ReadOnlyArrayList extends AbstractList {
    private final T[] array;
    private final int offset;
    private final int length;
    ReadOnlyArrayList(T[] array, int offset, int length) { ... }

    public static class Builder extends AbstractList {
        private T[] array = EMPTY_ARRAY;
        private int length;
        public void add(T t) { ... }
        public ReadOnlyArrayList build() {
            if (length == 0) return EMPTY_ARRAY_LIST;
            ReadOnlyArrayList r = new ReadOnlyArrayList(this.array, 0, this.length);
            this.array = null;
            this.length = 0;
            return;
        }
    }
}


Вместо ArrayList пользоваться классом нужно так:

ReadOnlyArrayList.Builder b = new ReadOnlyArrayList.Builder();
// add elements to b
return b.build();


В чём фишка класса ReadOnlyArrayList:

— класс immutable. Это очень хорошо, т. к. с кодом с неизменяемыми объектами гораздо проще работать
— список имеет константные по memory и cpu операции take(n) и drop(n), которые возвращают, опять же, объект типа ReadOnlyArrayList. Это очень удобно для многих алгоритмов. subList так же возвращает настоящий ReadOnlyArrayList, а не view
— в ReadOnlyArrayList не нужна дурацкая проверка на modCount. Она и в ArrayList не нужна, но тут проверка совсем не нужна
— создание списка по такой схеме дороже чем создание ArrayList на константу — создание экземпляра ReadOnlyArrayList.Builder
— хороший оптимизатор выделяет ReadOnlyArrayList.Builder на стеке
— для конструирования пустого списка не происходит выделения памяти (можно вернуть, например, ссылку на ReadOnlyArrayList.EMPTY_LIST)
— результирующий список всегда имеет один тип, что сильно облегчает оптимизацию (см. выше)
— и самое главное: производительность чтения из ReadOnlyArrayList равна ArrayList, потому что ReadOnlyArrayList имеет ровно такую же структуру, ровно те же поля, ровно такие же реализации методов, что и ArrayList

Я считаю, что в каждом проекте нужна какая-нибудь реализация ReadOnlyArrayList.

В библиотеке bolts есть реализация ReadOnlyArrayList (Builder называется ArrayListF, а метод build — convertToReadOnly).

Я буду лоббировать добавление в стандартную библиотеку Kotlin реализации класса ReadOnlyArrayList как "основного" класса коллекций в стандартной библиотеке, в т. ч. по-умолчанию для возврата из функций filter, map и т. п. (как это сделано в bolts).


U: Проверил, если заменить Collections.emptyList на shared empty ArrayList, а Collections.singletonList на новый ArrayList, тесты компилятора Kotlin ускоряются на 0,002.
Link21 comments|Leave a comment

Dependency Injection [Mar. 14th, 2012|12:00 am]

stepancheg
[Tags|, ]

Мы не осилили Guice. В добавок к описанным проблемам появилась ещё одна, очень существенная: Guice вызывает Class.getDeclaredMethods(). Этот метод не работает в аплетах, потому что security. Аплет у нас используется в web demo, чтобы типизировать код в браузере. Никаких способов обойти это ограничение я не нашёл.

В итоге сделали крутейшую вещь: генератор исходного кода, который делает dependency injection. AllInjectorsGenerator — это описание "контекстов". InjectorForTopDownAnalyzer — генерируемый код.

Нулевой runtime-overhead (это очень важно для компилятора, компилятор не имеет права стартовать по десять секунд, как некоторые серверные программы), ноль runtime библиотек, ошибки конфигурации контекста проверяются при сборке проекта, а не при запуске. Сила!
Link14 comments|Leave a comment

Юрьев день [Mar. 12th, 2012|07:34 pm]

stepancheg
Предлагаю ввести международный юрьев день для рефакторинга "переименование". Например, переименовывать классы можно только в последнюю пятницу месяца. А то кто-то улучшает код, а мне потом изменения в локальном патче мёрджить. Или наоборот, хочу что-то переименовать, а потом думаю, что мне два раза на кнопку нажать, а кому-то после этого два часа изменения мёрджить. А так был бы определённый день, все бы подготовились заранее, и все свои изменения закомитили.
Link5 comments|Leave a comment

navigation
[ viewing | most recent entries ]
[ go | earlier ]