Кто быстрее: memset, bzero или std::fill

    Есть мнение, что алгоритм std::fill() работает столько же эффективно на простых типах, как и старый добрый memset() (так как он его и использует в некоторых специализациях).

    Но порой не все так прозрачно, как хотелось бы. Рассмотрим программу.


    #include <cstdlib>
    #include <algorithm>
    
    int main(int argc, char* argv[]) {
      int mode = argc > 1 ? std::atoi(argv[1]) : 1;
      int n = 1024 * 1024 * 1024 * 1;
      char* buf = new char[n];
      if (mode == 1)
        std::memset(buf, 0, n * sizeof(*buf));
      else if (mode == 2)
        bzero(buf, n * sizeof(*buf));
      else if (mode == 3)
        std::fill(buf, buf + n, 0);
      else if (mode == 4)
        std::fill(buf, buf + n, '\0');
      return buf[0];
    }
    

    Обратите внимание на ветки 3 и 4. Они почти одно и то же, но не совсем.

    В целом была мысль получить вот эту специализацию fill():

    // Specialization: for one-byte types we can use memset.                      
    inline void                                                                   
    fill(unsigned char* __first, unsigned char* __last, const unsigned char& __c) 
    {                                                                             
      __glibcxx_requires_valid_range(__first, __last);                            
      const unsigned char __tmp = __c;                                            
      std::memset(__first, __tmp, __last - __first);                              
    }
    

    Итак, Makefile:

    all: build run
    
    .SILENT:
    
    target = memset_bzero_fill
    
    build:
    	g++ -O3 -o $(target) $(target).cpp
    
    run: run-memset run-bzero run-fill-1 run-fill-2
    
    go:
    	(time -p ./$(target) $(mode)) 2>&1 | head -1 | cut -d' ' -f 2
    
    run-memset:
    	echo $@ `$(MAKE) go mode=1`
    
    run-bzero:
    	echo $@ `$(MAKE) go mode=2`
    
    run-fill-1:
    	echo $@ `$(MAKE) go mode=3`
    
    run-fill-2:
    	echo $@ `$(MAKE) go mode=4`
    

    Компилятор «gcc version 4.2.1 (Apple Inc. build 5666) (dot 3)».

    Запускаем:

      run-memset 1.47
      run-bzero 1.45
      run-fill-1 1.69
      run-fill-2 1.42
    

    Видно, как ветка 3 (run-fill-1) значительно тормозит, по сравнению с 4, хотя разница всего в типе последнего параметра — 0 и '\0'.

    Смотрим ассемблер:

    (gdb) disass main
    Dump of assembler code for function main:
    0x0000000100000e70 <main+0>:	push   %rbp
    0x0000000100000e71 <main+1>:	mov    %rsp,%rbp
    0x0000000100000e74 <main+4>:	push   %r12
    0x0000000100000e76 <main+6>:	push   %rbx
    0x0000000100000e77 <main+7>:	dec    %edi
    0x0000000100000e79 <main+9>:	jle    0x100000ec3 <main+83>
    0x0000000100000e7b <main+11>:	mov    0x8(%rsi),%rdi
    0x0000000100000e7f <main+15>:	callq  0x100000efe <dyld_stub_atoi>
    0x0000000100000e84 <main+20>:	mov    %eax,%r12d
    0x0000000100000e87 <main+23>:	mov    $0x40000000,%edi
    0x0000000100000e8c <main+28>:	callq  0x100000ef8 <dyld_stub__Znam>
    0x0000000100000e91 <main+33>:	mov    %rax,%rbx
    0x0000000100000e94 <main+36>:	cmp    $0x1,%r12d
    0x0000000100000e98 <main+40>:	je     0x100000eac <main+60>   ; mode == 1
    0x0000000100000e9a <main+42>:	cmp    $0x2,%r12d
    0x0000000100000e9e <main+46>:	je     0x100000eac <main+60>   ; mode == 2
    0x0000000100000ea0 <main+48>:	cmp    $0x3,%r12d
    0x0000000100000ea4 <main+52>:	je     0x100000ed2 <main+98>   ; mode == 3
    0x0000000100000ea6 <main+54>:	cmp    $0x4,%r12d
    0x0000000100000eaa <main+58>:	jne    0x100000ebb <main+75>   ; mode != 4 -> выход
    
    ; Реалиазация через memset().
    
    0x0000000100000eac <main+60>:	mov    $0x40000000,%edx        ; mode = 1, 2 или 4
    0x0000000100000eb1 <main+65>:	xor    %esi,%esi
    0x0000000100000eb3 <main+67>:	mov    %rbx,%rdi
    0x0000000100000eb6 <main+70>:	callq  0x100000f0a <dyld_stub_memset>
    
    0x0000000100000ebb <main+75>:	movsbl (%rbx),%eax             ; выход
    0x0000000100000ebe <main+78>:	pop    %rbx
    0x0000000100000ebf <main+79>:	pop    %r12
    0x0000000100000ec1 <main+81>:	leaveq 
    0x0000000100000ec2 <main+82>:	retq   
    
    0x0000000100000ec3 <main+83>:	mov    $0x40000000,%edi
    0x0000000100000ec8 <main+88>:	callq  0x100000ef8 <dyld_stub__Znam>
    0x0000000100000ecd <main+93>:	mov    %rax,%rbx
    0x0000000100000ed0 <main+96>:	jmp    0x100000eac <main+60>
    
    ; Реализация на обычных командах.
    
    0x0000000100000ed2 <main+98>:	movb   $0x0,(%rax)             ; mode = 3
    0x0000000100000ed5 <main+101>:	mov    $0x1,%eax
    0x0000000100000eda <main+106>:	nopw   0x0(%rax,%rax,1)
    0x0000000100000ee0 <main+112>:	movb   $0x0,(%rax,%rbx,1)
    0x0000000100000ee4 <main+116>:	inc    %rax
    0x0000000100000ee7 <main+119>:	cmp    $0x40000000,%rax
    0x0000000100000eed <main+125>:	jne    0x100000ee0 <main+112>
    
    0x0000000100000eef <main+127>:	movsbl (%rbx),%eax             ; выход
    0x0000000100000ef2 <main+130>:	pop    %rbx
    0x0000000100000ef3 <main+131>:	pop    %r12
    0x0000000100000ef5 <main+133>:	leaveq 
    0x0000000100000ef6 <main+134>:	retq
    

    Видно, что благодаря оптимизации, ветки 1, 2 и 4 реализованы одинаково — через memset(). Вызов fill() в ветке 4 удалось свести к memset().

    Но вот ветка 3 реализована в виде ручного цикла. Компилятор, конечно, неплохо поработал — цикл практически идеальный, но это все равно работает медленнее, чем хитрый memset(), который использует всякие ухищрения групповых ассемблерных операций.

    Неверный тип нуля не дал компилятору правильно выбрать специализацию шаблона.

    Мораль? И мораль тут не очень хорошая.

    Мне кажется, что количество людей, которые напишут «std::fill(buf, buf + n, 0)», разительно больше, чем «std::fill(buf, buf + n, '\0')».

    А разница весьма существенна.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 73
    • +4
      банальная цитата о преждевременной оптимизации
      • НЛО прилетело и опубликовало эту надпись здесь
      • +13
        Если вы обнуляете данные гигабайтами, то вы что-то делаете не так :)
        Мне очень трудно представить когда это является узким местом в программе.
        • НЛО прилетело и опубликовало эту надпись здесь
          • –3
            практически все известные мне менеджеры памяти не обнуляют страницы, а помечают их как свободные.
            • НЛО прилетело и опубликовало эту надпись здесь
              • +6
                Да, вы правы, на нулевом приоритете крутится поток MmZeroPageThread, который билдид список MmZeroedPageListHead.
                Но никак не гигабайтами, и только во время абсолютного простоя, ибо ниже приоритета 0 нету ничего :)
                Так что это слабо опровергает мои слова о том, что необходимо заострять внимание на этом аспекте.
                Например, в вышеупомянутой винде код очистки крайне прост.
                Дефолтный код:
                .text:00430364 __fastcall KiZeroPages(x, x) proc near ; CODE XREF: MiZeroWorkerPages(x,x)+10Dp
                .text:00430364 ; KiXMMIZeroPages(x,x)+B5j ...
                .text:00430364 push edi
                .text:00430365 xor eax, eax
                .text:00430367 mov edi, ecx
                .text:00430369 mov ecx, edx
                .text:0043036B shr ecx, 2
                .text:0043036E rep stosd
                .text:00430370 pop edi
                .text:00430371 retn
                .text:00430371 __fastcall KiZeroPages(x, x) endp


                И второй вариант с XMMI (extended memory managment):
                .text:00430288 __fastcall KiXMMIZeroPagesNoSave(x, x) proc near
                .text:00430288 ; CODE XREF: KiXMMIZeroPages(x,x)+84p
                .text:00430288 ; DATA XREF: KiInitMachineDependent()+1F0o
                .text:00430288
                .text:00430288 var_4 = dword ptr -4
                .text:00430288
                .text:00430288 xorps xmm0, xmm0
                .text:0043028B shr edx, 6
                .text:0043028E
                .text:0043028E loc_43028E: ; CODE XREF: KiXMMIZeroPagesNoSave(x,x)+19j
                .text:0043028E movntps xmmword ptr [ecx], xmm0
                .text:00430291 movntps xmmword ptr [ecx+10h], xmm0
                .text:00430295 movntps xmmword ptr [ecx+20h], xmm0
                .text:00430299 movntps xmmword ptr [ecx+30h], xmm0
                .text:0043029D add ecx, 40h
                .text:004302A0 dec edx
                .text:004302A1 jnz short loc_43028E
                .text:004302A3 sfence
                .text:004302A6 xchg edx, [esp+var_4]
                .text:004302AA retn
                .text:004302AA __fastcall KiXMMIZeroPagesNoSave(x, x) endp


                • НЛО прилетело и опубликовало эту надпись здесь
                  • +2
                    еще раз.
                    речь идёт о производительности обнуления памяти.
                    программа работает месяц и за это время очистила пару гигов памяти, нужно ли оптимизировать, раз очищаются такие здоровые объемы памяти? Или это тоже считается «узким местом»? По-моему ответ очевиден, и не стоит передергивать мои слова :)

                    А на первый вопрос ответ можно глянуть в открытых сорцах ядра винды:
                    
                        //
                        // The following code sets the current thread's base priority to zero
                        // and then sets its current priority to zero. This ensures that the
                        // thread always runs at a priority of zero.
                        //
                    
                        KeSetPriorityZeroPageThread (0);
                    
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • 0
                        > Я вот на это ответил
                        а вторую часть не такого уж длинного комментария не заметили, или это так и надо выдирать фразы из контекста? :)
                        > Покрутите немного колесиком мыши…
                        опять же, я не имею в виду конкретное приложение (у вас — виндовый MM), а отвечаю абстрактно на простой вопрос («Если обнуляет медленно и не постоянно, то не считается?»)
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • 0
                            Допустим это приложение очень быстро чем нибудь управляет, в этом случае нужно оптимизировать обнуление этого несчастного килобайта, будь он неладен.

                            Если этот несчастный нулевой килобайт — итоговая информация, то оптимизировать надо протокол, а не код и не алгоритм :) Если же поверх этого нулевого килобайта перед выдачей пишется что-то осмысленное, то время, потраченное на вычисления будет несоизмеримо больше времени обнуления.
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • 0
                                Множество ситуаций, где обнуление памяти — вполне осмысленная операция и множество ситуаций, где скорость этого обнуления критична по времени, не пересекаются.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • 0
                                    Ну тогда приведите пример, где обнуление памяти является вполне осмысленной операцией и одновременно с этим заметно влияет на общую скорость.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                      • 0
                                        И это обнуление внесёт заметный вклад в общее время операции?
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                          • 0
                                            Вряд ли быстрее, чем этот ключ будет передаваться от клиента, даже если это RPC внутри машины, а не сеть.

                                            Да и случай с обнулением ключа мне кажется притянутым — зачем его обнулять? приложение защищается от самого себя?
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                        • 0
                                          Обнулять массивы можно. Речь о том, что это обнуление либо изначально не является критичным по времени, либо его можно сделать таковым, не прибегая к оптимизации самой реализации обнуления.
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                            • 0
                                              Плохо — не должно. Хорошо — тоже :) Потому что с практической точки зрения оно фиолетово даже если бы компилятор не сводил всё к memset'у. И дело не в быстрых процессорах, а в соотношении количества обнулений с количеством и стоимостью вычислений поверх обнулённого. Оптимизировать обнуление имеет смысл только с эстетической точки зрения.
                                              • 0
                                                Привет из 2015 ,) 4 года назад я как-то пропустил эту полемику и теперь жуть, как интересно, что было в удаленных НЛО комментариях :) Только криптография? То же касается ветки ниже про виртуальную память.
                                    • 0
                                      Есть мнение что всё что можно оптимизировать можно оптимизировать.
                                      • 0
                                        oops: простите, нужно оптимизировать! :)
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                          • 0
                                            Это даже не рекурсия, а просто тождественно истинное утверждение :)
                                            • 0
                                              агу, компилятор НЛО его может оптимизировать, и выкинет :)
                            • 0
                              Да пусть сколько влезет обнуляет, хоть петабайты. Его вообще можно убрать и ничего не поломается ;) разве что VirtualAlloc'и чуть дольше будут отрабатывать, что опять же является критичным местом только в очень неадекватном коде.
                              • НЛО прилетело и опубликовало эту надпись здесь
                                • 0
                                  Вообще-то VirtualAlloc работает с виртуальными страницами (если только не указан MEM_PHYSICAL, что есть экзотика). А хотел я сказать то, что при MEM_COMMIT он by design возвращает обнулённый кусок памяти. Нафига — по идее ради того чтобы не нарваться на ошмётки данных/паролей из чужого отработавшего процесса. Обсуждаемый idle thread нужен только для того чтобы VirtaulAlloc в большинстве случаев получал уже обнулённый кусок, когда выделяемая страница попадает на физическую память.
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                    • 0
                                      Физические страницы, принадлежавшие ранее одному приложению, не могут быть прочитаны другим приложением, пока они не попадут в его адресное пространство. А попасть они туда могут только через VirtualAlloc.

                                      Таким образом, если обсуждаемого idle thread'а не будет или он не получит процессорного времени между смертью первого приложения и запуском второго (точнее между VirtualFree первого и VirtualAlloc второго), обнулением придётся заниматься VirtualAlloc'у.

                                      Обнуляющий поток нужен только для того, чтобы в большинстве случаев VirtualAlloc получал уже обнулённую физическую страницу и не тратил на это обнуление процессорное время выделяющего память приложения.
                                      • 0
                                        Тут дело в том, что из-за нулевого приоритета этого обнуляющего потока возможны ситуации, когда VirtualAlloc нарвётся на необнулённую страницу — и её всё равно придётся обнулять, но уже не из этого потока, и из VirtualAlloc'а (его kernel-mode части) — т.е. эта логика в VirtualAlloc'е по-любому есть. Назначение idle thread'а — избавить большинство VirtualAlloc'ов от этого обнуляющего цикла, выполняя обнуление в фоне и помечая физические страницы как обнулённые. Если этот флаг стоит — VirtualAlloc ничего не делает. Если флага нет, обнулением занимается VirtualAlloc.
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                          • +1
                                            Ок, не только в VirtualAlloc, но и во всех остальных ситуациях, выделяющих физическую страницу под кусок адресного пространества, будет стоять проверка на обнулённость страницы и если флага обнулённости не окажется, будет выполняться обнуление на месте.

                                            Суть моего повествования в том что idle thread может не успеть ничего обнулить — и, таким образом, может быть вообще исключен из системы, не влияя на её работоспособность. При этом приложения будут и дальше получать обнулённые физические страницы в результате VirtualAlloc'ов, page fault'ов и т.п. Нзначение этого idle thread'а — сугубо оптимизирующее, а не краеугольнее.
                      • +6
                        Вася и Петя зателнетелись на один сервер. Вася отредактировал секретный файлик и отлогинился. Хитрый Петя, написал программу, затребовал много памяти, и нашел в памяти файлик отредактированный Васей… Ведь менеджеры памяти не обнуляют свободные страницы, как Вы думаете?
                        • –1
                          мне кажется если бы вся освобожденная память обнулялась, то в выделенном новом куске должны быть одни нули, не так ли? В жизни такого не наблюдал, более того пример выше с файликом не так далек от реальности.
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • НЛО прилетело и опубликовало эту надпись здесь
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • НЛО прилетело и опубликовало эту надпись здесь
                      • +1
                        Ну например, многодорожечный аудиомикшер, в программе есть несколько десятков звукогенераторов, которые в реальном времени обрабатывают, каждый, скажем, под 192 КГц / 2 канала = 384000 сэмплов * 4 байта (или даже 8, щас модно на double микширование делать) в секунду, и перед каждым заполнением им нужно занулять свои буферы, потом суммировать туда что-то (алгоритм не всегда позволяет просто перезаписывать буфер). Естественно, это всё равно будет не самое узкое место в программе, но пара % загрузки процессора улетит тупо на нагрев воздуха. И если «преждевременно» не думать о таких мелочах, то потом проект уходит на профилирование и оптимизацию на месяц.
                        • +1
                          никогда этим не интересовался, но неужели эти 400к сэмплов нужны одновременно?
                          простой подсчет показывает что только под эти буферы нужно будет 200-300 метров памяти, неужели микшер столько кушает?
                          • 0
                            Конечно нет, если микшер не на иммутабельных списках.
                            Каждый моно канал кушает размер asio буфера, ну или другой подсистемы; от 256 (асио и крутая звуковуха) до пары десятков тысяч (вин мм) отсчетов, даблов в данном случае. Чем меньше, чем лучше — меньше задержка, и тем меньше нужно памяти. Плюс буфер для суммы, который конечно не нужно обнулять — он перезаписывается блин.
                      • +1
                        Интересно. Спасибо.
                        А попробуйте, пожалуйста, для расширения мистического опыта добавить в ключи компиляции "-march=native -mtune=native" — интересно, появятся ли дополнительные оптимизации вроде использования SSE. Только не забудьте написать, какая у вас машина.
                        • +1
                          Может я чего не понимаю, но не слишком ли брутально не освобождать гиг памяти?
                          • +4
                            Так программа завершится, и вся выделенная память будет уничтожена (т.к. будет уничтожена куча приложения). К слову — уничтожение кучи происходит быстрее, чем отдельных её составляющих, а потом и самой кучи ;)
                            • 0
                              точно, моя невнимательность:)
                              • 0
                                Только вот memory leak детекторы будут ругаться все равно. В данном конкреном случае это не так важно, потому что это тест, а вот в реальном приложении из-за таких ошибок можно не заметить реальный лик. К тому же разве имеет особое значение для вашего приложения сколько времени займет его завершение?
                                • 0
                                  Это ясно, я и не призывал не удалять память :) А вот насчёт времени завершения — не соглашусь. Тратится процессорное время на то, что в общем-то не нужно. С точки зрения логики — зачем вызывать удаление памяти, если завершается приложение? А потом получаются Оперы и Фаерфоксы, котрые после закрытия окна ещё очищают память в течение значительного времени, тратя ресурсы. Но проблема далеко не тривиальная, поэтому её игнорируют.
                                  • 0
                                    «К тому же разве имеет особое значение для вашего приложения сколько времени займет его завершение?»

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

                                    ps: я не в коей мере не занудствую просто при написании больших программных комплексов (а по хорошему в любой программе) надо учитывать такие моменты.
                                  • +1
                                    всё так, но только в куче максимальный размер куска — 512Kb, в x64 — 1 метр, для больших кусков (если размер кучи не лимитирован) используется прямое выделение памяти через VirtualAlloc, и ваше утверждение про скорость уничтожения становится неверным :)
                                    • 0
                                      Вы думаете, что вызвать delete/free для каждого объекта, а затем удаление кучи будет не медленнее удаления сразу всей кучи? Да, если объекты большие и их немного, то разница незаметна. А вот если выделено много маленьких объектов, то удаление всей кучи происходит значительно быстрее.
                                      • 0
                                        ну речь идёт об освобождение гига памяти)
                                        • 0
                                          Ну, я там поставил «к слову» именно с целью уточнить, что не в данном случае, но возможны варианты. Наверное недосточно точно выразился.
                                • +1
                                  В MSVS 2010 все наоборот:

                                  // TEMPLATE FUNCTION fill
                                  template< class _FwdIt, class _Ty >
                                  inline void _Fill(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
                                  { // copy _Val through [_First, _Last)
                                      for (; _First != _Last; ++_First)
                                          *_First = _Val;
                                  }
                                   
                                  inline void _Fill(char *_First, char *_Last, int _Val)
                                  { // copy char _Val through [_First, _Last)
                                      _CSTD memset(_First, _Val, _Last - _First);
                                  }
                                   
                                  inline void _Fill(signed char *_First, signed char *_Last, int _Val)
                                  { // copy signed char _Val through [_First, _Last)
                                      _CSTD memset(_First, _Val, _Last - _First);
                                  }
                                   
                                  inline void _Fill(unsigned char *_First, unsigned char *_Last, int _Val)
                                  { // copy unsigned char _Val through [_First, _Last)
                                      _CSTD memset(_First, _Val, _Last - _First);
                                  }


                                  т.е. через memset выполняется 3-я ветка, а четвертая через цикл.
                                  • 0
                                    Это еще не все. Померил время выполнения в разных режимах компиляции (SSE on/off и т.п.) и очень удивился, когда время для всех режимов оказалось одинаковым. Изучение сгенерированного кода показало, что во всех случаях вызывается memset. Даже для того случая, когда в debug версии явно виден цикл (первый вариант функции в моем комментарии выше).
                                    • 0
                                      Или цикл как то странно приводится к memset или таки используется одна из специализаций:
                                      ; 8    :   if (mode == 1)
                                      
                                        00031	83 ff 01	 cmp	 edi, 1
                                        00034	75 16		 jne	 SHORT $LN5@main
                                      $LN32@main:
                                      
                                      ; 9    :     std::memset(buf, 0, n * sizeof(*buf));
                                      
                                        00036	68 00 00 00 40	 push	 1073741824		; 40000000H
                                      
                                      ; 15   :     std::fill(buf, buf + n, '\0');
                                      
                                        0003b	6a 00		 push	 0
                                        0003d	56		 push	 esi
                                        0003e	e8 00 00 00 00	 call	 _memset
                                      
                                      ; 16   :   return buf[0];
                                      
                                        00043	0f be 06	 movsx	 eax, BYTE PTR [esi]
                                        00046	83 c4 0c	 add	 esp, 12			; 0000000cH
                                        00049	5f		 pop	 edi
                                        0004a	5e		 pop	 esi
                                      
                                      ; 17   : }
                                      
                                        0004b	c3		 ret	 0
                                      $LN5@main:
                                      
                                      ; 10   : //  else if (mode == 2)
                                      ; 11   : //    bzero(buf, n * sizeof(*buf));
                                      ; 12   :   else if (mode == 3)
                                      
                                        0004c	83 ff 03	 cmp	 edi, 3
                                      
                                      ; 13   :     std::fill(buf, buf + n, 0);
                                      
                                        0004f	74 e5		 je	 SHORT $LN32@main
                                      
                                      ; 14   :   else if (mode == 4)
                                      
                                        00051	83 ff 04	 cmp	 edi, 4
                                        00054	75 18		 jne	 SHORT $LN26@main
                                      
                                      ; 15   :     std::fill(buf, buf + n, '\0');
                                      
                                        00056	8d 86 00 00 00
                                      	40		 lea	 eax, DWORD PTR [esi+1073741824]
                                        0005c	3b f0		 cmp	 esi, eax
                                        0005e	74 0e		 je	 SHORT $LN26@main
                                        00060	2b c6		 sub	 eax, esi
                                        00062	50		 push	 eax
                                        00063	6a 00		 push	 0
                                        00065	56		 push	 esi
                                        00066	e8 00 00 00 00	 call	 _memset
                                        0006b	83 c4 0c	 add	 esp, 12			; 0000000cH
                                      $LN26@main:
                                      
                                      ; 16   :   return buf[0];
                                      
                                        0006e	0f be 06	 movsx	 eax, BYTE PTR [esi]
                                        00071	5f		 pop	 edi
                                        00072	5e		 pop	 esi
                                      
                                      


                                      Как видно, первая и третья ветка вообще используют один и тот же код, а четвертая — после странной проверки делает все тот же memset.
                                      • +1
                                        Странная проверка делается чтобы не вызывать memset, если n == 0 (хотя, в данном случае ее можно вообще выкинуть, почему компилятор до этого не додумался непонятно). А цикл да, мистическим образом превращается в memset, более того, следующий код тоже превратится в memset (!):

                                        for( auto p = buf; p != buf + n; ++)
                                            *= '\0';
                                        00DA107D  lea         eax,[esi+40000000h]  
                                        00DA1083  cmp         esi,eax  
                                        00DA1085  je          f+95h (0DA1095h)  
                                        00DA1087  sub         eax,esi  
                                        00DA1089  push        eax  
                                        00DA108A  push        0  
                                        00DA108C  push        esi  
                                        00DA108D  call        memset (0DA1D64h)  
                                        00DA1092  add         esp,0Ch  


                                        • +2
                                          Это в очередной раз подтверждает, что зря заминусовали первый комментарий.
                                        • +2
                                          Выходит в VS2010 оптимизатор более продвинутый.
                                          • +1
                                            Не, это давно появилось (в 2к5, кажется) — ещё тогда некоторые ругались, пытаясь отвязать CRT-less прогу от memset'a.
                                      • +1
                                        Во первых у вас погрешность измерения сопоставима с результатами измерения. Используйте rtdsc.
                                        Во вторых мемсетить лучше double* через SSE и мимо кэша (movntpd).
                                        В третьих поиграйтесь лучше с memcpy() — это более полезно…
                                        • 0
                                          Да мне тоже не понятно, как человек знающий зачем есть «disas» в gdb может измерять время через ./time prog
                                          Есть valgrind, есть vtune, которые (помимо всего прочего) отлично меряют затраченные циклы процессора.
                                        • –1
                                          Поговорку про преждевременную оптимизацию не слышали?
                                          • +2
                                            Первый комментарий в топике не читали?
                                          • 0
                                            Конечно нет, если микшер не на иммутабельных списках.
                                            Каждый моно канал кушает размер asio буфера, ну или другой подсистемы; от 256 (асио и крутая звуковуха) до пары десятков тысяч (вин мм) отсчетов, даблов в данном случае. Чем меньше, чем лучше — меньше задержка, и тем меньше нужно памяти. Плюс буфер для суммы, который конечно не нужно обнулять — он перезаписывается блин.

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.