forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   JAVA Micro Edition (http://forum.boolean.name/forumdisplay.php?f=52)
-   -   Смесь: Неочевидное + Оптимизация (http://forum.boolean.name/showthread.php?t=19580)

Жека 15.12.2014 12:26

Смесь: Неочевидное + Оптимизация
 
Вложений: 3
Всем привет!

Я сейчас занимаюсь оптимизацией приложения: j2me-клиент для мобильной соц. сети Galaxy.
(можете загуглить, не буду рекламировать)

Решил создать эту тему, чтобы делиться наблюдениями, слушать советы и всё такое прочее.

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

При старте приложения - главный экран - съедается порядка 1 мб.
Остаётся ещё 1 до ошибки "Out of memory".

Что есть в приложении:
* непрерывный коннект к чат-серверу по сокету
* самодельный браузер, псевдо-хтмл, много картинок - грузятся по хттп.
* планета с персонажами (костюмы, одежда), объектами, фоном/интерьером
* чатик
Вложение 21254 Вложение 21255 Вложение 21256

Масштабы проекта:
* 25 packages
* 177 классов и интерфейсов суммарно
Например, в пакете browser 50 классов/интерфейсов.

Что заметно жрёт оперативку:
* создание новых объектов, в частности строк (парсинг страниц, получение команд из сокета)
* картинки (браузер и планета)

Я юзаю ручной вызов GC (сборщик мусора), и по возможности стараюсь перехватить переполнение памяти через
catch(OutOfMemoryError out) {}
но не всегда срабатывает, прога крэшится и закрывается.

Т.к. j2me - это младшая, но родная, сестра "обычной" java, то тема может быть полезна широкому кругу читателей. :)

Далее отдельными постами рассказ.

Жека 15.12.2014 13:25

Ответ: Смесь: Неочевидное + Оптимизация
 
0. Для справки

Внутренние объекты java при дефолтном создании:

* Vector - элементы хранятся в массиве.
v = new Vector() - выделяется память под 10 элементов, при выходе за границы внутренний массив вектора пересоздаётся, расширяясь вдвое (могу наврать тут, может и не в 2)

* Hashtable
h = new Hashtable() - размерность таблицы равна 11, хз почему так

* StringBuffer - данные хранятся во внутреннем массиве chars[]
sb = new StringBuffer() - размерность равна 16


1. Коннект и реконнект к чат-серверу

Раньше
при каждом подключении или переподключении создавался новый поток, в котором новый сокет.

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


2. Загрузка ресурсов по http

Раньше
а) на каждый запрос картинки создавался отдельный поток в лоадере
б) для каждой загрузки страницы создавался новый лоадер + поток в нём

Теперь
есть всего 1 бесконечный поток лоадера, внутрь которого вкидываются задачи на загрузку ресурсов.
Пока остаётся под вопросом реализация ajax-запросов, наверное для аякса придётся мастерить отдельный поток, чтобы отклик не зависел от текущих запросов на загрузку других ресурсов.


3. LinkedList vs Vector

Стараюсь по возможности заменить использование Vector на LinkedList, чтобы не тратились ресурсы при расширении размерности вектора.
* LinkedList - мой самодельный двунаправленный список, ибо в комплекте j2me списков нет.
Vector хорош, когда нужен произвольный доступ к элементам по индексу в массиве.


4. Шрифты

Используются рисованные шрифты. При загрузке красятся в несколько цветов.
Для кнопочной версии приложения красим, подменяя цвета в палитре, для сенсорной версии переводим картинку в rgba[], красим через hsv преобразование, и далее создаём из полученных new_rgba[] новую картинку.

Раньше
для каждого шрифта грузились все данные, картинки + описательные структуры данных, массивы смещений символов и массивы ширин букв и прочее

Теперь
для одинаковых шрифтов, различающихся только цветом, грузим описательные структуры только 1 раз, остальным шрифтам присваиваем указатели на эти структуры.
В частности, в шрифте есть массивы
* private int arrWidth[];
* private int arrBlock[];
* private int arrOffset[];
Количество символов = 337. На 1 шрифт приходится 337*3*4 = 4044 байта. Умножим это на 5 крашенных вариантов и получим 20,2 кб. гуд!



5. Смайлы, ч1

Раньше мы использовали анимированные смайлы.
Пришлось для хранения смайла использовать класс View, который умеет проигрывать анимацию.
View содержит набор Pic
Pic содержит ImageObject
ImageObject содержит Image (это джавовская картинка)
* ImageObject - обёртка над стандартной картинкой, хранить ключ-url и саму картинку, и немного служебной инфы

Раньше
каждый смайл имел тип View

Теперь
каждый смайл имеет тип ImageObject
Это позволило съэконосить 70 кб памяти!
В любом случае, буду делать рефактор классов View и Pic.


6. Смайлы, ч2
В данный момент пытаюсь улучшить функцию разбивки текста на текст и смайлы.
RegExp'ов в j2me нет, да они и оперативы жрут скорее всего больше доступного.
Всего есть 76 текстовых вариантов смайлов.

Сейчас
1. проходим по всем смайлам, ищём их в строке, запоминаем самый ближний к началу строки
2. делим строку на текст до смайла (если есть), сам смайл (если есть) и остаток строки снова подаём на вход пункта 1.

Пока что сделал предварительную проверку по 4 символам, которые уникальны для смайлов - : ; % ) и др.
Для строк без смайлов это даёт прирост скорости раз в 7. Со смайлами - замедляет, но не сильно, т.к. джавовский string.IndexOf() работает очень шустро.


Сложение строк

На днях я обнаружил опасность.
При сложении строк компилятор подставляет туда StringBuffer.
s = s+"1024"; превратится в
s = new StringBuffer().append(s).append(1024).toString();

Bроде всё круто, но при этом StringBuffer будет пересоздавать/расширять внутренний массив, и мы получаем в оперативке массив, почти вдвое больший чем сумма наших исходных строк!

В джаве строки - это структуры со ссылкой на массив chars[] и переменными start и len.

Если текст жирный, например 10+ кб, как браузерная страница, то прибавлением к строке любого символа расширит строку до двукратного размера.

Можно обрезать лишние символы в оперативке через
s = new String(s)
если на них больше никто не ссылается, но они всё равно будут живы до очередного вызова сборщика мусора.


Продолжение следует. :)

Жека 16.12.2014 09:03

Ответ: Смесь: Неочевидное + Оптимизация
 
Улучшение поиска смайлов в тексте. Завершил.

Фишки новой функции:
* предварительный поиск смайловых символов (даёт ускорение только если в тексте нет смайлов)
* поиск сразу всех смайлов, сортировка по позиции смайла в тексте делается сразу

Т.к. текстовые варианты некоторых смайлов являются подстрокой других, например "0 : )" или "{ : )" включают в себя ": )", то пришлось делать проверку - не попадает ли очередной найденный смайл в позицию уже найденных.
Проверка - это цикл по найденным смайлам. Раз уж никуда не деться от этого цикла, то в нём делаю проверку соотнесения позиции текущего найденного смайла с найденными ранее, тем самым определяю позицию для вставки, т.е. не нужно потом сортировать все смайлы по позиции.

Зачем нужна сортировка? Чтобы можно было указывать лимит смайлов, свыше которого всё считается обычным текстом.

Победа:
* время поиска сократилось в 2 раза
* объём потребляемой памяти сократился в 3 раза

Конечно, в реальности цифры могут быть чуть больше и чуть меньше, но в целом ожидаю именно такой прогресс.

Тестирование:
Для теста взял некий текст (под спойлером) длиной 639 символов, содержащий в середине 4 смайла.

"Женщины - как яблоки . Самые вкусные висят на самой макушке дерева. Многие Мужчины не хотят лезть на дерево за вкусными яблоками, потому что они боятся упасть и удариться. Вместо этого они собирают упавшие яблоки с земли, которые не так хороши, но зато доступны. Поэтому яблоки на макушке думают, что с ними, что-то не так, хотя на самом деле - ОНИ ВЕЛИКОЛЕПНЫ! Женщины - как яблоки :-.. Самые вкусные:) висят на самой макушке:( дерева. :) Им просто нужно дождаться того человека, который не побоится залезть на макушку дерева. Отправь это сообщение Женщинам, которых ты ЛЮБИШЬ! Милая, красивая, нежная! ЛЮБИ И БУДЬ ЛЮБИМА! Счастья тебе! "

Поиск производился в цикле, 500 итераций.

Результаты теста:

* новая функция, тест №1:
использовано памяти перед тестом: 943 кб
время выполнения: 1836 мс
память после теста: 1254 кб

* старая функция, тест №2:
использовано памяти перед тестом: 943 кб
время выполнения: 4361 мс
память после теста: 1896 кб

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

Спасибо за внимание!

Жека 16.12.2014 10:59

Ответ: Смесь: Неочевидное + Оптимизация
 
Загрузка текста из файла

Джава хорошо дружит с кодировкой UTF-8.
В ней на один символ приходится от 1 до 2 байтов.

Когда-то давно я пробовал загрузить текст из файла, и возникли проблемы.
В итоге я извратился, сделав свой формат строки, в котором символ представлен по типу джавовского:
size|byte1[byte2]

Так и жил с этим.
А недавно, делая проект на андроид, и пытаясь там грузить УТФ-текст из файла, я обнаружил, что в начале строки вылазит какой-то непонятный символ, но остальная часть строки нормальная.
Сделал самое простое: t = t.substring(1)
Но так как этот символ есть не всегда, то пришлось в начало добавлять спец. символ, типа #, и далее
i = t.indexOf('#');
t = t.substring(i);

Как-то это стрёмно, вставлять символ, выцеплять подстроку.

А потом обнаружил в Notepad++ кодировку "UTF-8 without BOM", решил сохранить в неё, и вуаля - всё чётко, только мой текст!

Ещё:

* в Qt строки в utf-8 сохраняются без BOM.
* в php чтение из файла - тоже лучше использовать без BOM, иначе левый символ в начале
* ответ на стековерфлоу: What's different between utf-8 and utf-8 without BOM?

Даёшь хранение текста в формате "UTF-8 without BOM"!

Моя функция загрузки текста:

PHP код:

public static String loadText(final String path) {
    try {
        final 
InputStream is Utils.class.getResourceAsStream(path);
        final 
DataInputStream dis = new DataInputStream(is);
        
int size dis.available();
        
byte b[] = new byte[size];
        
int actual dis.read(b);
        
dis.close();
        
is.close();
        
String s = (new String(b0actual"UTF-8")).trim();
        return 
s;
    }
    catch(
Exception e) {}
    return 
null;



impersonalis 16.12.2014 11:23

Ответ: Смесь: Неочевидное + Оптимизация
 
Цитата:

Сообщение от Жека (Сообщение 290708)
Джава хорошо дружит с кодировкой UTF-8.
В ней на один символ приходится от 1 до 2 байтов.

Но ведь в UTF-8 символ, в общем случае, может занимать от 1 до 6 байт. Или "в ней"="в джаве"? (хотя на вряд ли будут использоваться символы из не базовой плоскости)

Жека 16.12.2014 12:08

Ответ: Смесь: Неочевидное + Оптимизация
 
imper, да я попутал, спасибо за уточнение!

Жека 16.12.2014 13:03

Ответ: Смесь: Неочевидное + Оптимизация
 
Hashtable

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

Зато это избавит меня от пересоздания "внутренностей" хэш-таблицы при достижении maxCapacity.

Вот статья для углубленного изучения: java.util.Hashtable from inside (на русском).

Igor 16.12.2014 14:55

Ответ: Смесь: Неочевидное + Оптимизация
 
Может, не перебором, а хотя бы бинарный? Отсортировать по ключам, если, конечно, нет частых вставок и удалений.
И ещё - для поиска смайликов можно всё-таки сделать конечный автомат. Не знаю как по памяти, но по скорости выполнения должен быть сильно лучше.

Жека 16.12.2014 19:41

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

Igor, можешь дать ссылок или описать суть того, как конечный автомат применить к смайлам?

Igor 16.12.2014 20:17

Ответ: Смесь: Неочевидное + Оптимизация
 
Во всяких парсерах для компиляторов активно используется.
моё видение того как оно работает:

есть текст и смайлики ": )", ": (" и "{ : )"
посимвольно читаем текст, есть несколько состояний, входной символ в переменной "с"
1) (начальное состояние)
(с == ":") -> 2
(c == "{") -> 3
иначе подаем на выход c и не меняем состояние
2)
(с== ")"), выдаём смайлик :) и переходим в 1
(с == "("), выдаём смайлик :( и переходим в 1
(с== "{") выводим ":" (это не кусок смайлика), переходим в 3
иначе выводим ":" + c и переходим в 1
3)
(с == ":") -> 4
иначе выводим "{" + c и переходим в 1
4)
(с == ")") выводим смайл "{:)", переходим в 1
(c == "("), выводим "{" + ":(", переходим в 1
иначе выводим "{:" + c и переходим в 1.

Если всё просто, можно захардкодить вручную, если сложно, то пишут штуку, которая это делает сама.

Жека 17.12.2014 05:46

Ответ: Смесь: Неочевидное + Оптимизация
 
Понял суть, спасибо.
Хардкодить не наш метод, ибо 76 комбинаций, начиная от : ) и заканчивая :troll:
Всё же эти посимвольные проверки окажутся медленнее чем поиск подстроки всей текстовой комбинации смайла (инфа почти 100%).

Волшебный String.indexOf ()

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

Что за проверки? Зачем?

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

Для них и были проверки некие в цикле по тексту.
PHP код:

char c text.charAt (k);
if (
>= '0' && <= '9') {
    
block DIGITS;
    
offset k;


И так для всех диапазонов (русские, англ, закорючки).

Однако! Это оказалось медленнее, чем
PHP код:

offset ALL_FONT_CHARS.indexOf (text.charAt (k)); 

ALL_FONT_CHARS содержит все доступные для отрисовки символы шрифта.

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

Кстати, буквы ёЁ стоят особняком от остальных русских букв в таблице символов, что совсем нектати.

impersonalis 17.12.2014 12:31

Ответ: Смесь: Неочевидное + Оптимизация
 
Цитата:

Сообщение от Жека (Сообщение 290752)
Кстати, буквы ёЁ стоят особняком от остальных русских букв в таблице символов

в CP-1251 (Windows-1251)

Жека 17.12.2014 13:02

Ответ: Смесь: Неочевидное + Оптимизация
 
Копирование массивов, System.arrayCopy()

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

У себя в коде использую

* при расширении массивов, создаём массив большего размера, копируем в него текущий массив (надо бы избавиться от этих expand'ов)

* при чтении байтов из сокета

Чтение из сокета:

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

С давних пор у меня делался сдвиг содержимого этого буфера от последней неполной команды в начало буфера:
Код:

for(int k = lastPos; k < size; ++k) {
    buf[k-lastPos] = buf[k];
}

//size = 2048 байт
//lastPos - позиция неполной команды

Сравнил скорость присвоения в цикле и arrayCopy, копирование побеждает.

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

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

Официальное описание функции arrayCopy() (english).

Жека 17.12.2014 13:07

Ответ: Смесь: Неочевидное + Оптимизация
 
Цитата:

Сообщение от impersonalis (Сообщение 290760)
в CP-1251 (Windows-1251)

разве только там? вот например таблица utf http://unicode-table.com/ru/

impersonalis 17.12.2014 15:55

Ответ: Смесь: Неочевидное + Оптимизация
 
Цитата:

Сообщение от Жека (Сообщение 290766)
разве только там? вот например таблица utf http://unicode-table.com/ru/

Не только, но и не везде. Если речь только о unicode - то ок

Жека 18.12.2014 09:56

Ответ: Смесь: Неочевидное + Оптимизация
 
Загрузка советов - "лишние" 400 кб RAM

В проге есть советы, которые показываются в окне "ожидайте...", пока грузится страница браузера.

В данный момент советов много, суммарное количество символов текста около 5900. т.е. примерно 5900*4 = 23,6 кб.

Есть версионность, т.е. советы присылаются из сокета только если в клиенте устаревшая версия. Иначе достаём из кэша.

Выяснил вот что.
1. До загрузки советов занято 1130 кб оперативки
2. Каждый совет в среднем жрёт 1-2 кб
3. На момент загрузки всех советов из сокета занято 1205 кб
4. После завершения функции tipsPrepare() занято 1548 кб
5. После очередного вызова сборщика мусора занято 1110 кб

Принцип добавления советов
1. Каждый совет присылается в отдельной команде
2. При получении команды добавляем совет в список (LinkedList)
3. Добавляем все советы
4. Создаём строковый массив tips, из которого будем рандомно выцеплять совет для показа
5. В цикле проходим по списку с советами, заполняя массив и формируя длинную строку, содержащую все советы, чтобы далее сохранить эту строку в кэш (внутр. память)
6. чистим список советов (хотя это особо ничего не даёт, т.к. ссылки на строки есть в массиве)

Самое прожорливое место - формирование строки для сохранения в кэш. Опять это "дурацкое" пересоздание массива chars внутри StringBuffer'a при сложении строк.
PHP код:

tipsString "";
for(
listTips.start(); listTips.hasElement(); listTips.next(), ++k) {
    
tips[k] = (String)listTips.item();
    if(
tipsString.length() > 0)
        
tipsString += "\r\n";
    
tipsString += tips[k];


После этого цикла оперативка доходит до 1496 кб.

Далее идёт сохранение в кэш, которое и доводит до финального уровня 1548 кб.

Решение, оптимизация

Сделал переменную int tipsStrLen, в которую плюсую длину каждого совета при его добавлении в список, и юзаю StringBuffer заранее указанной длины:
PHP код:

StringBuffer buf = new StringBuffer(tipsStrLen);
int k 0;
for(
listTips.start(); listTips.hasElement(); listTips.next(), ++k) {
    
tips[k] = (String)listTips.item();
    if(
0) {
        
buf.append("\r\n");
    }
    
buf.append(tips[k]);
}
tipsString buf.toString(); //получаем строку для сохранения в кэш 

В результате имеем:

* tips.prepared: 1218 кб
* tips. saved: 1269 кб (тут без изменений, главное что суммарно занято теперь намного меньше)

Ура, товарищи! :)

Замечания:
1. Советы меняются не слишком часто, т.е. обычно грузятся из кэша, при этом такой перерасход памяти не наблюдается. В основном грузятся при первом входе в чат, но ничего не мешает получить список команд при любом очередном перелёте на планету.
2. Храню в кэше всё одной строкой через \r\n, при загрузке разбиваю строку на отдельные советы. Возможно, лучше хранить отдельно каждый совет.
3. При каждом перелёте на планету советы заново создавались из строки, взятой из кэша, это память почти не расходует, но это лишняя ненужная операция*. Тоже поправил.

* время разбивки строки на компе неинформативно, а на мобилке замерять лень.

Жека 13.01.2015 09:35

Ответ: Смесь: Неочевидное + Оптимизация
 
Замутил удобоваримые для мозга сортировки образов внутри объектов.

Образы сортируются сначала по глубине depth - это слой рисования: -1 задний, 0 средний, 1 передний; затем по addOrder - порядку добавления образа в объект.

Т.к. я отказался от массивов в пользу списков, то пришлось переделывать сортировку.
Вот что получилось.

LinkedList.sortBubble()
PHP код:

public void sortBubble(final IComparator comparator) {
    if(
count 2) { return; }
    
boolean dirty true;
    while(
dirty) {
        
Link node1 first;
        
Link node2 node1.next;
        
dirty false;
        while(
true) {
            
int r comparator.compare(node1.itemnode2.item);
            if(
0) {
                
Object tmp node1.item;
                
node1.item node2.item;
                
node2.item tmp;
                
dirty true;
            }
            
node1 node2;
            
node2 node2.next;
            if(
node2 == null) {
                break;
            }
        }
    }





Было:
PHP код:

public static void sortViews_Depth_AddOrder(final View views[]) {
    if(
views.length 2) { return; }
    
int max;
    
int valprev=0;
    
View tmp;
    final 
int mas[] = new int[views.length];
    
int index 0;
    
//сортировка по depth
    
for(int k views.length ; ++k) {
        
max views[k].depth();
        for(
int j k+views.length ; ++j) {
            
val views[j].depth();
            if(
val max) {
                
tmp views[j];
                
views[j] = views[k];
                
views[k] = tmp;
                
max val;
            }
        }
        if(
&& max != prev)
            ++
index;
        ++
mas[index];
        
prev max;
    }
    
//сортировка по addOrder
    
index 0;
    
int end 0;
    for(
int k views.length ; ++, ++index) {
        if(
mas[index] < 2//если один с таким значением, то он уже на своём месте
            
continue;
        
end k+mas[index];
        for(
int j end ; ++j) {
            
max views[j].addOrder();
            for(
int i j+end ; ++i) {
                
val views[i].addOrder();
                if(
val max) {
                    
tmp views[i];
                    
views[i] = views[j];
                    
views[j] = tmp;
                    
max val;
                }
            }
        }
    }



здесь в массив mas (писал ещё в те времена, когда массивы называл mas, а не arr) накапливаем количество одинаковых чисел, идущих подряд, чтобы потом отсортировать среди них по addOrder.

рекламный слоган: "Тройной FOR - это по-нашему!" :)

Стало:
PHP код:

private static DepthAddOrderComparator depthAddOrderComparator;
public static 
void sortViews_Depth_AddOrder(final LinkedList views) {
    if(
views.count() < 2) { return; }
    if(
depthAddOrderComparator == null) {
        
depthAddOrderComparator = new DepthAddOrderComparator();
    }
    
views.sortBubble(depthAddOrderComparator);




А вот наш чудесный "комплексный" компаратор, который включает в себя сравнение по двум параметрам - depth и addOrder.
PHP код:

public class DepthAddOrderComparator implements IComparator {

    public 
int compare(Object o1Object o2) {
        
ViewObject vo1 = (ViewObject)o1;
        
ViewObject vo2 = (ViewObject)o2;
        
View v1 vo1.view();
        
View v2 vo2.view();
        
int d1 v1.depth();
        
int d2 v2.depth();
        if(
d1 == d2) {
            
//если глубина одинакова, то нужно сортировать по очерёдности добавления
            
int a1 v1.addOrder();
            
int a2 v2.addOrder();
            if(
a1 == a2) {
                return 
0;
            } else if(
a1 a2) {
                return 
1;
            } else {
                return -
1;
            }
        } else if(
d1 d2) {
            return 
1;
        } else {
            return -
1;
        }
    }





Добавление условий
PHP код:

if(d1 == d2) {
    
//если глубина одинакова, то нужно сортировать по очерёдности добавления
    
int a1 v1.addOrder();
    
int a2 v2.addOrder();
    if(
a1 == a2) {
        return 
0;
    } else if(
a1 a2) {
        return 
1;
    } else {
        return -
1;
    }


позволяет избавиться от монстра в виде
PHP код:

//сортировка по addOrder
index 0;
int end 0;
for(
int k views.length ; ++, ++index) {
    if(
mas[index] < 2//если один с таким значением, то он уже на своём месте
        
continue;
    
end k+mas[index];
    for(
int j end ; ++j) {
        
max views[j].addOrder();
        for(
int i j+end ; ++i) {
            
val views[i].addOrder();
            if(
val max) {
                
tmp views[i];
                
views[i] = views[j];
                
views[j] = tmp;
                
max val;
            }
        }
    }



MiXaeL 13.01.2015 21:23

Ответ: Смесь: Неочевидное + Оптимизация
 
Не думаю, что очень часто возникает ситуация, когда вектор расширяется и потом не заполняется элементами. Т.е. выигрываешь в памяти не часто, а вот в скорости может быть заметный проигрыш, т.к. вектор быстрее списка, даже на операциях вставки в середину не очень больших объектов. А всё потому что кэш процессора (данные лежат последовательно в памяти). Для справки цитата из презенташки характерное время доступа к памяти в тиках процессора:
1 cycle to read a register
4 cycles to reach to L1 cache
10 cycles to reach L2 cache
75 cycles to reach L3 cache
and hundreds of cycles to reach main memory.

Т. е. я бы потестировал еще внимательно работу вектора и списка.

Igor 13.01.2015 21:41

Ответ: Смесь: Неочевидное + Оптимизация
 
В стандартном Vector каждый метод synchronized - теоретически, из-за этого может быть медленнее.
А вообще, можно посмотреть исходники и на их основе написать свою версию, заточенную под требования конкретного случая.

Жека 14.01.2015 06:38

Ответ: Смесь: Неочевидное + Оптимизация
 
Цитата:

Сообщение от MiXaeL (Сообщение 291732)
Не думаю, что очень часто возникает ситуация, когда вектор расширяется и потом не заполняется элементами. Т.е. выигрываешь в памяти не часто, а вот в скорости может быть заметный проигрыш, т.к. вектор быстрее списка, даже на операциях вставки в середину не очень больших объектов. А всё потому что кэш процессора (данные лежат последовательно в памяти).

1. если вектору не указать capacity increment (а я никогда его не указываю), то при расширении он станет в 2 раза больше. поэтому шанс не заполниться при добавлении 1 объекта - большой
2. вставка в середину. в джаве есть arrayCopy, сдвигается с его помощью, наверное поэтому шустро.
я обычно вставляю в список или удаляю через ссылку на узел (ListNode), т.е. по какому-то условию в цикле, когда у меня есть этот самый узел,
а это быстро, т.к. нужно просто переприсвоить ссылки next, prev
PHP код:

for (views.start(); views.hasElement(); views.next()) {
    
ViewObject v = (ViewObject)views.item();
    if(
v.id() == id) {
        
views.remove(views.node());
        break;
    }


3. как обстоят дела с кэшем в мобилках я мало представляю, наверное тоже что-то есть

Минус списка - создаётся мелочёвка в виде узлов ListNode для каждого элемента, сборщику мусора нужно будет чистить эти узлы после удаления элементов.

Igor, спасибо что напомнил про synchronized.

Жека 15.01.2015 14:56

Ответ: Смесь: Неочевидное + Оптимизация
 
Решил переделать циклы перебора списков.

Было так:
PHP код:

for(list.start() ; list.hasElement() ; list.next()) {
    
CustomObject item = (CustomObject)list.item();
    
//что-то делаем с объектом item


т.е. тут есть завязка на внутренний бегунок current внутри списка, который движется от начала списка first, до конца списка current == null.

такой код потенциально опасен - если в цикле обхода списка вызывается функция, которая должна пробегать по этому же списку, то бегунок current становится неправильным.
для решения я сделал (залипуху/костыль/______) в виде дополнительной переменной storedCurrent и два метода store/restore, полагая, что более глубокой вложенности не будет.
глубже вложенности пока нет, но нафиг надо. лишнее поле, работающее не универсально (вот если бы был стек сохраняемых бегунков, то другое дело), и нужное лишь для одного места в коде.

Стало так:
PHP код:

for(ListNode node = list.first(); node != nullnode node.next) {
    
CustomObject item = (CustomObject)node.item;
    
//что-то делаем с объектом item


что тут скажешь - классика.
проход в обратном порядке делается аналогично, node = list.last();
node.next, node.item - да, они public

Примечание: иногда нужно пропустить очередной next, в этом случае можно либо писать так:
PHP код:

for(ListNode node = list.first(); node != null/**/) {
    
CustomObject item = (CustomObject)node.item;
    
//что-то делаем с объектом item
    
if(condition) continue;

    
node node.next;


или с помощью вспомогательных функций next и skipNext.
PHP код:

for(ListNode node = list.first(); node != nullnode = list.next(node)) {
    
CustomObject item = (CustomObject)node.item;
    
//что-то делаем с объектом item
    
if(condition) list.skipStep();
}

......
//LinkedList.java

public void skipStep() {
    
isNeedSkipStep true;
}
public 
ListNode next(final ListNode node) {
    if(
isNeedSkipStep) {
        
isNeedSkipStep false;
        return 
node;
    }
    return 
node.next;


Доселе я в некоторых местах использовал skip.
Теперь хочу отказаться от этого, чтобы не было путаницы.
пример: вызываешь в цикле метод list.skipStep(), надеешься что он не подведёт,
а в условии цикла про это забываешь, делая простой node = node.next;

UPD: большинство списков требуют только однопаправленный проход.
Есть мысль сделать односвязный список и юзать его по максимуму.
Неудобство - функция удаления узла, т.к. из текущего узла никак не выцепить предыдущий, чтобы перекинуть дальше его ссылку next.
Скорее всего сделаю этот список, в функцию удаления буду передавать два узла - подлежащий_удалению и предыдущий_для_него.
Удалений элементов не слишком много, все они в цикле, т.е. можно легко хранить внешний указатель на prev.

Жека 15.02.2015 19:20

Ответ: Смесь: Неочевидное + Оптимизация
 
Переписал HTML- и JSON- парсеры

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

В инете я нашёл парсер json для j2me. Но он мне показался слишком громоздким, дофигища классов.
В итоге я написал свой, всего 100 строк кода (со второй попытки, первая попытка была страшновата для неподготовленного мозга).
Скорость работы и потребляемую память не сравнивал с "интернетовским".

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

А вот html парсер я сравнивал со своим же, который юзаю в текущей версии. Пока что сравнение сделал для страницы регистрации (другие популярные страницы - на очереди).

текущий
время: 29 мс
память: 20 кб

новый
время: 12-15 мс
память: 13 кб

Сравнивал на ПК-эмуляторе, это конечно не качественно. Потом проверю на мобильнике.

Ещё из фишек нового парсера - можно делать неограниченную вложенность тегов.
Текущий не умеет делать вложенные однотипные теги, например таблицу в таблицу нельзя или span в span. Теперь можно.
Ну и новый тег style добавили, теперь можно стили юзать, это должно сократить код, облегчить вёрстку.

Жека 24.04.2015 10:57

Ответ: Смесь: Неочевидное + Оптимизация
 
Уже давненько открыл для себя в нетбинсе фичу автоподстановки, которая предлагает варианты сразу при написании первой буквы.
По дефолту эта фича выключена.
Очень ускоряет кодинг.
Скрин.

Жека 25.06.2015 20:33

Ответ: Смесь: Неочевидное + Оптимизация
 
Мы зарелизили новую версию - Galaxy 9.0!
Не сегодня, пораньше. Вчера я уже допилил багфиксы для 9.0.4.

К делу. На некоторых телефонах прога закрывалась сразу при запуске, выдавая NullPointerException.
Включая 2 наших тестовых телефона, и это хорошо - было на чём разбираться. Сроки горели, пришлось с багом релизить.
(Но мы не афишировали обнову сразу, лишь спустя несколько дней), так что не было поголовных апдейтов.
Тем более что это j2me, нет автообновлений.

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

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

Комбинируя waitForKey с выводом на экран каждого шага загрузки ресурсов, я обнаружил,
что проблема в функции загрузки текста из файла:

PHP код:

public static String loadText(final String path) {
    try {
        final 
InputStream is Utils.class.getResourceAsStream(path);
        final 
DataInputStream dis = new DataInputStream(is);
        
int size dis.available();
        
byte[] = new byte[size];
        
int actual dis.read(b);
        
dis.close();
        
is.close();
        
String s = (new String(b0actual"UTF-8")).trim();
        return 
s;
    } catch (
Exception ex) {
        
ex.printStackTrace();
    }
    return 
null;




Изучил её внутренности, вроде всё в порядке, но я выдвинул гипотезу-причину, переделал, но не помогло. :)
[гипотеза: некоторые телефоны выдают некорректную длину данных в потоке: int size = dis.available();]

Дальнейший пошаговый дебаг выявил окончательную причину.
Так в чём же тут дело?

Ответ:
Выполнение функции попадало в catch с типом IOException, на строчке закрытия стрима, dis или is - кого именно я выяснять не стал.
Сделал вывод: некоторые телефоны, закрывая DataInputStream, автоматом закрывают и InputStream.
Решение: закрывать стримы в секции finally, оборачивая снова в try...catch....


Попутно узнал, что в конструкции try - catch - finally секция finally выполнится, даже если в try есть досрочный return.
PHP код:

private boolean abcd() {
    try {
        
Midlet.sout("try to do");
        return 
true;
    } catch (
Exception e) {
        
Midlet.sout("exception");
    } finally {
        
Midlet.sout("finally"); // этот код выполняется
    
}
    return 
false;



Жека 03.12.2015 12:54

Ответ: Смесь: Неочевидное + Оптимизация
 
Узнал интересную штуку для дебага - определение вложенности вызовов функций:
Код:

try {
    throw new Exception("test");
} catch (Exception e) {
    e.printStackTrace();
}

Генерируем исключение и печатаем его стек.
Блин, это гениально!

Помогло мне разобраться в длинной череде вызовов:

Код:

java.lang.Exception: test
        at galaxy.browser.Page.imageAddToDownloads(Page.java:1463)
        at galaxy.browser.ElementImage.prepare(ElementImage.java:165)
        at galaxy.browser.Element.prepare(Element.java:287)
        at galaxy.browser.ElementFactory.updateParams(ElementFactory.java:269)
        at galaxy.browser.ElementFactory.insert(ElementFactory.java:190)
        at galaxy.browser.ElementFactory.insert(ElementFactory.java:183)
        at galaxy.browser.ElementFactory.createElementImage(ElementFactory.java:941)
        at galaxy.browser.ElementFactory.createElement(ElementFactory.java:72)
        at galaxy.browser.ElementFactory.fillContainer(+113)
        at galaxy.browser.ElementFactory.fillContainer(+6)
        at galaxy.browser.ElementSpan.prepare(ElementSpan.java:48)
        at galaxy.browser.ElementFactory.updateParams(ElementFactory.java:269)
        at galaxy.browser.ElementPlank.updateInnerElements(ElementPlank.java:153)
        at galaxy.browser.ElementPlank.prepareInners(ElementPlank.java:77)
        at galaxy.browser.ElementPlank.layout(ElementPlank.java:164)
        at galaxy.browser.ElementContainer.layout(ElementContainer.java:243)
        at galaxy.browser.ElementTable.layout(ElementTable.java:432)
        at galaxy.browser.ElementContainer.layout(ElementContainer.java:243)
        at galaxy.browser.ElementPages.showPage(ElementPages.java:265)
        at galaxy.browser.ElementPages.activate(ElementPages.java:223)
        at galaxy.browser.ElementPages.layout(ElementPages.java:328)
        at galaxy.browser.ElementContainer.layout(ElementContainer.java:243)
        at galaxy.browser.Page.layout(Page.java:640)
        at galaxy.browser.Page.create(Page.java:172)
        at galaxy.browser.Browser.createPage(+34)
        at galaxy.browser.Browser.createPage(+48)
        at galaxy.browser.Browser.createPage(+6)
        at galaxy.browser.Browser.loadPageOffline(+28)
        at galaxy.Main.browserShowOffline(+39)
        at galaxy.Main.showTestPage(+136)
        at galaxy.Main.run(+94)


Жека 03.02.2016 09:52

Ответ: Смесь: Неочевидное + Оптимизация
 
Прокрутка браузерных страниц.

Раньше у меня при прокрутке страницы делался сдвиг всех элементов формы.
В итоге для жирных страниц совершалось много теледвижений, проходов по вложенным тегам.

Теперь просто сдвигаю канвас при отрисовке страницы.

Дополнительно пришлось
* поправить функцию setClip, т.к. при сдвиге канваса область отсечения также уезжает.
* поправить пункцию isElementInView, т.к. раньше в ней завязка была на то что меняем координаты самих элементов.

Жека 08.02.2016 14:23

Ответ: Смесь: Неочевидное + Оптимизация
 
С функциями graphics.setClip() и graphics.translate() оказалось больше проблем, чем хотелось бы.

Прокачал класс Viewport - хэлпер для работы с клиппингом. Н-р, он умеет делать lock/unlock, чтобы последующие установки клипа не выходили за эту залоченную область.

Что узнал:

1. Важен порядок вызова функций
setClip => translate != translate => setClip
при установке области клипа учитывается текущий сдвиг координат, но все последующие никак не влияют, запоминает именно сдвиг в момент установки клипа.

2. Реальные координаты клипа g.getClipX() и g.getClipY() - возвращаются значения с учётом сдвига координат.

3. g.translate(int x, int y) - это не передвинуть канвас в указанную точку, а сдвинуть на указанное количество относительно текущей позиции. могли бы написать в доке dx, dy для понятности.

4. Внутренности графикса по части клипа и транслэйта сложнее, чем думал (см. скрин)

Жека 05.05.2016 07:19

Ответ: Смесь: Неочевидное + Оптимизация
 
Если кто-то использует NetBeans IDE и любит тёмное оформление, то можете установить Darcula плагин.
https://github.com/Revivius/nb-darcula

Он делает тёмным весь интерфейс, а не только область кода, как некоторые.
Портал с плагинами недоступен, пришлось компилить из исходников.
скачать из дропбокса nb-darcula-1.5.nbm


Часовой пояс GMT +4, время: 10:46.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot