Re: Быстрый способ обнулить блок памяти?
От: av_coder Украина  
Дата: 11.09.11 20:40
Оценка:
N>PS Кроме SSE инструкций (заполнять по 16 байт) ничего нет?

еще есть AVX инструкции там 32 байта
Re[2]: Быстрый способ обнулить блок памяти?
От: ononim  
Дата: 11.09.11 20:52
Оценка: 1 (1)
N>>Добрый день, коллеги.
N>>Подскажите самый быстрый способ обнулить блок памяти (1000000 байт)?
R>самый бйстрый способ его просто выкинуть и выделить новый через virtualalloc
R>там он гарантированно будет забит нулями и занимается этим system idle process
VirtualAlloc то сам по себе может и окажется быстрее чем memset на уже выделенную память, но вот последующее первое обращение к этой памяти... Ой ой ой. Поясняю на живом примере:
int wmain(int argc, wchar_t* argv[])
{
        volatile char *p = (volatile char *)malloc(0x100000);
        DWORD ticks = ::GetTickCount();
        for (int i = 0; i<10000; ++i)
        {
            memset((void *)p, 0, 0x100000);
        }
        printf("memset time: %u msec\n", ::GetTickCount() - ticks);
        free((void *)p);
        
        ticks = ::GetTickCount();
        for (int i = 0; i<10000; ++i)
        {
            volatile char *p = (volatile char *)::VirtualAlloc(0, 0x100000, MEM_COMMIT, PAGE_READWRITE);
            ::VirtualFree((void *)p, 0, MEM_RELEASE);
        }
        printf("VM time: %u msec\n", ::GetTickCount() - ticks);
        return 0;
}

Результаты:
memset time: 641 msec
VM time: 47 msec



Смеемся и плачем от счастья.. Потом думаем — ведь Virtual* теоретически подразумевают собой нехиый оверхед.. Идти в ядро, ковыряцца в таблицах страниц, дереве VAD процесса, Чота тут не так..
Слегка модифицируем тест:
void TouchMemory(volatile char *p, size_t l)
{
    while (l>=0x1000)
    {
        *p = *p;
        l-= 0x1000;
        p+= 0x1000;
    }
};

int wmain(int argc, wchar_t* argv[])
{
        volatile char *p = (volatile char *)malloc(0x100000);
        DWORD ticks = ::GetTickCount();
        for (int i = 0; i<10000; ++i)
        {
            memset((void *)p, 0, 0x100000);
            TouchMemory(p, 0x100000);
        }
        printf("memset time: %u msec\n", ::GetTickCount() - ticks);
        free((void *)p);
        
        ticks = ::GetTickCount();
        for (int i = 0; i<10000; ++i)
        {
            volatile char *p = (volatile char *)::VirtualAlloc(0, 0x100000, MEM_COMMIT, PAGE_READWRITE);
            TouchMemory(p, 0x100000);
            ::VirtualFree((void *)p, 0, MEM_RELEASE);
        }
        printf("VM time: %u msec\n", ::GetTickCount() - ticks);
        return 0;
}

Теперь результаты:
memset time: 688 msec
VM time: 2953 msec


Вот все и встало на свои места.. Итак винда похоже тоже практикует ленивую отдачу страниц, так что постоянный VirtualAlloc будет быстрее однократного malloc + многократного memset тока если не не планируете этой памятью пользоваться Справедливости ради, если делать многократные malloc/memset/free то оно все же медленее чем VM... Во всяком случае пока у винды не наступит жесткий голодняк свободных и уже обнуленных в бэкграундее страниц.
Как много веселых ребят, и все делают велосипед...
Re[3]: Быстрый способ обнулить блок памяти?
От: rm822 Россия  
Дата: 11.09.11 22:16
Оценка:
O>Вот все и встало на свои места.. Итак винда похоже тоже практикует ленивую отдачу страниц
валлок будет все время выдавать разные блоки памяти.
Ты всего лишь сравнил скорость зануления 1мб блока в кэше процессора vs 1мб блок в оперативке.
Понятно что в кэше-то их занулять намного быстрее.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: Быстрый способ обнулить блок памяти?
От: ononim  
Дата: 12.09.11 06:39
Оценка:
O>>Вот все и встало на свои места.. Итак винда похоже тоже практикует ленивую отдачу страниц
R>валлок будет все время выдавать разные блоки памяти.
R>Ты всего лишь сравнил скорость зануления 1мб блока в кэше процессора vs 1мб блок в оперативке.
R>Понятно что в кэше-то их занулять намного быстрее.
Даже если и изза кэша, все равно медленно ведь
Как много веселых ребят, и все делают велосипед...
Re[7]: Быстрый способ обнулить блок памяти?
От: watch-maker  
Дата: 12.09.11 13:32
Оценка: 4 (2)
Здравствуйте, Pzz, Вы писали:

N>>Поясните еще раз эту мысль ("А из кеша в память льется струей" — к чему это).

N>>Я, конечно пойду читать про кеш, но я думал я заполняю память по указателю непосредственно.

Pzz>Вы пишете в кеш. Это такая маленькая быстрая память, припаянная прямо к процессору. Процессор потом, по своему усмотрению и без вашего участия, перемещает данные из кеша в память — да еще и с промежуточным буферированием в контроллере памяти.


Pzz>Из кеша в контроллер памяти данные уходят блоками размером с cache line (это минимальная единица обмена между кешом и системной памятью). У современных x86-х процессоров кеш-лайн имеет размер 64 байта. Контроллер же памяти старается склеить эти обращения так, чтобы набрать целую "строку": работа "строками" — это самый быстрый способ работы с памятью. Чему сейчас равна строка, я даже и не знаю.


На самом деле запись в кеш не обязательна для объединения множественных записей в одну транзакцию.
Конечно, в кеш писать можно очень быстро. И если ваша цель сделать быструю функцию memset, то смело пишите в кеш.
Вот только проблема тут в том, что такая функция будет при своей работе вытеснять из кеша другие полезные данные. В результате чего получается ситуация, когда функция memset работает быстро, но код после её вызова начинает внезапно тормозить — причина проста, быстрый memset выкинул из кеша нужные в дальнейшем данные, оставив в нём лишь много-много нулей.
И понятно, что хотелось бы не только оптимизировать memset, но и не замедлять работу окружающего кода (ведь в жизни важна суммарная производительность программы, а не отдельных её кусочков).
Для этого в x86 процессорах есть streamming-инструкции (семейство movnt*), которые служат для записи в память минуя кеш. Грубо говоря, у этих инструкций есть как бы собственный кеш, но который не пересекается с основным, и для которого даже не поддерживается неявная когерентность. То есть можно записывать те же 16 байт из sse регистнов в этот буфер, где они будут объединятся в транзакции, и после этого записываться в память строками, минуя опять же кеш. Если данных в память записывается много (очевидно, что если мы обнуляем количество байт сравнимое с размером L2 кеша, то это уже много), то имеет смысл именно писать через streamming-инструкции, ведь в этом случае кешированная версия memset никак не будет быстрее чем streamming-версия, ибо первая версия будет даже сама себя из кеша вытеснять, а вторая просто будет лить данные прямо в память.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.