понедельник, 19 июля 2010 г.

Что делать, если не хватает памяти?

Ситуация редкая, но, к сожалению, очень актуальная для тех, кто еще не перешел на x64 архитектуру: имеется x86 программа, требовательная к памяти, установлено достаточно ОЗУ (4-8 Гб), но процесс хоть и должен получать порядка 2 ГБ останавливается на 1.7 Гб и дальше либо активно свопит (мучает GC в случае .Net), либо падает с ошибками OOM.

Конечно, можно потратить сколько-то времени и сделать полноценный порт под x64, но для Windows есть еще один "ход конем", позволяющий выделить программе вплоть до 4Гб памяти на х64 системе и до 3Гб на x86. Это актуально как для .Net, так и программ на C++ и других языках.

Сама операция очень проста - берем утилиту EditBin из комплекта Visual C++ 2005/2008/2010 и выполняем команду
editbin.exe OurExe.exe /LARGEADDRESSAWARE

Наличие изменений можно посмотреть сравнив бинарный файл с оригиналом командой fc /bin.

На x86 системах может получиться, что не будет эффекта, пока не будет прописан ключ /3gb в файле boot.ini:
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /fastdetect /3GB


Никакой особой магии и шаманства в этой операции нет, все это давно описано на microsoft.com

Дальше..

понедельник, 5 июля 2010 г.

Tips for Geeks. Часть 1

Периодически буду выкладывать тут различные советы для любителей оптимизировать все, что можно и что нельзя. :) Нередко прирост производительности от подобных решений минимален, но иногда встречаются задачи, где важен каждый такт процессорного времени. Большая часть типсов связана с программированием на C++, но могут встречаться и алгоритмические нюансы, применимые к любому языку.


Итого, первый tip. Перебор массива байт.

На первый взгляд, что может быть проще? К примеру, подсчет суммы всех байт массива на С++:

int summ(char * data, size_t length)
{
int result = 0;
for(size_t i=0;i<length;i++)
result+=data[i];
return result;
}

Нормальному человеку я порекомендую оставить этот код как есть. Он работает очень быстро на современных компьютерах и при этом идеально читабелен.
Если же вспомнить, что мы живем в эпоху 32-битных вычислений, а на пороге уже и 64-битные, то оперировать 8-битными числами получается как-то несерьёзно и расточительно.
Я опущу долгие рассуждения и приведу сразу финальный код:

int summ(char * data, size_t length)
{
int result = 0;
int nlength = length & ~3;

size_t i = 0;
for(; i < nlength; i += 4)
for(size_t j=0; j < 4; j++)
result += data[i + j];

for(; i < length; i++)
result += data[i];

return result;
}

Как видно по коду, операция прохода по массиву выполняется в два больших цикла и когда, к примеру, length = 18, первый цикл будет идти от 0 до 15, а второй - от 16 до 17. Цикл с переменной j будет развернут в присваивания со смещением буквально любым компилятором, потому на нем можно не заострять внимание.
Приведенный код приблизительно в полтора раза быстрее побайтового перебора, но при этом гораздо менее читабелен. Еще лучше будет результат если использовать шаг не 4, а 8 байт, но тут уже прирост не так заметен. Если же стоит задача считать не просто сумму, а выполнять на лету шифрование (XOR с константой или номером элемента и пр.), то производительность уже будет зависеть именно от внутренних операций, а не от организации перебора. При этом, чем сложнее код, тем больше ошибок в нём можно допустить.

Мораль же сей басни такова:
Premature optimization is the root of all evil. (c) Knuth, Donald. 1974

Очень многим людям я бы посоветовал распечатать эту аксиому и повесить на стену в сами видном месте.

Дальше..