64-разрядная проблема производительности Linux с memset
Я отлаживаю приложение, которое работает немного медленнее, когда оно построено как 64-разрядный исполняемый файл ELF Linux, чем как 32-разрядный исполняемый файл Linux ELF. Используя Rational (IBM) Quantify, я отслеживал большую часть разницы в производительности вплоть до (барабанный ролл...) memset
. Как ни странно, memset
занимает лот дольше в 64-битном исполняемом файле.
Я даже могу видеть это с помощью небольшого простого приложения:
#include <stdlib.h>
#include <string.h>
#define BUFFER_LENGTH 8000000
int main()
{
unsigned char* buffer = malloc(BUFFER_LENGTH * sizeof(unsigned char));
for(int i = 0; i < 10000; i++)
memset(buffer, 0, BUFFER_LENGTH * sizeof(unsigned char));
}
Я строю вот так:
$ gcc -m32 -std=gnu99 -g -O3 ms.c
и
$ gcc -m64 -std=gnu99 -g -O3 ms.c
Время настенных часов, о котором сообщает time
, больше для сборки -m64
, а Quantify подтверждает, что дополнительное время расходуется на memset
.
До сих пор я тестировал VirtualBox и VMWare (но не Linux для Linux, я понимаю, что мне нужно сделать это дальше). Количество лишнего времени, по-видимому, немного меняется от одной системы к другой.
Что здесь происходит? Есть ли известная проблема, которую мой Google-foo не может раскрыть?
EDIT: Разборка (gcc ... -S
) в моей системе показывает, что memset
вызывается как внешняя функция:
32-бит:
.LBB2:
.loc 1 14 0
movl $8000000, 8(%esp)
.loc 1 12 0
addl $1, %ebx
.loc 1 14 0
movl $0, 4(%esp)
movl %esi, (%esp)
call memset
64-бит:
.LBB2:
.loc 1 14 0
xorl %esi, %esi
movl $8000000, %edx
movq %rbp, %rdi
.LVL1:
.loc 1 12 0
addl $1, %ebx
.loc 1 14 0
call memset
Система:
- CentOS 5.7 2.6.18-274.17.1.el5 x86_64
- GCC 4.1.2
- Intel (R) Core (TM) i7-2600K CPU @3,40 ГГц /VirtualBox
(несоответствие хуже на Xeon E5620 @2,40 ГГц /VMWare )
Ответы
Ответ 1
Я могу подтвердить, что на моей не виртуализированной системе Mandriva Linux версия x86_64 немного (около 7%) медленнее. В обоих случаях вызывается функция библиотеки memset()
, независимо от размера слова набора команд.
Случайный взгляд на ассемблерный код обеих реализаций библиотек показывает, что версия x86_64 значительно сложнее. Я предполагаю, что это в основном связано с тем, что 32-разрядная версия должна иметь дело только с 4 возможными случаями выравнивания по сравнению с 8 возможными вариантами выравнивания 64-разрядной версии. Также кажется, что цикл x86_64 memset()
был более развернут, возможно, из-за различных оптимизаций компилятора.
Другим фактором, который мог бы учитывать более медленные операции, является увеличение нагрузки ввода-вывода, связанное с использованием размера слова в 64 бита. Как код, так и метаданные (указатели e.t.c.) обычно становятся больше в 64-битных приложениях.
Кроме того, имейте в виду, что реализации библиотек, включенные в большинство дистрибутивов, ориентированы на любой процессор, который, по мнению разработчиков, является самым низким общим знаменателем для каждого семейства процессоров. Это может оставить 64-разрядные процессоры в невыгодном положении, так как 32-разрядный набор команд был стабильным в течение некоторого времени.
Ответ 2
Я считаю, что виртуализация является виновником: я сам выполнял некоторые тесты (генерация случайных чисел в массовом порядке, последовательный поиск, а также 64-разрядная версия), и выяснил, что код работает в 2 раза медленнее в Linux в VirtualBox, чем изначально под окнами. Самое забавное: код не делает ввода/вывода (за исключением простого printf сейчас, а затем, между таймингами) и использует небольшую память (все данные вписываются в кеш L1), поэтому можно подумать, что вы можете исключить управление таблицами страниц и TLB накладные расходы.
Это действительно таинственно. Я заметил, что VirtualBox сообщает VM, что инструкции SSE 4.1 и SSE 4.2 не поддерживаются, хотя ЦП поддерживает их, а программа, использующая их, работает отлично (!) В виртуальной машине. У меня нет времени, чтобы исследовать проблему дальше, но вы ДЕЙСТВИТЕЛЬНО должны время ее на реальной машине. К сожалению, моя программа не будет работать на 32 бита, поэтому я не смог бы протестировать замедление в 32-битном режиме.
Ответ 3
При компиляции кода примера компилятор видит фиксированный размер блока (~ 8 МБ) и решает использовать версию библиотеки. Попробуйте код для гораздо меньших блоков (для memset'ing всего несколько байтов) - сравните разборку.
Хотя я не знаю, почему версия x64 медленнее. Я думаю, что в вашем коде измерения времени есть проблема.
Из журнал изменений gcc 4.3:
Была переписана генерация кода перемещения блока (memcpy) и набора блоков (memset). Теперь GCC может выбрать лучший алгоритм (цикл, развернутый цикл, команда с префиксом rep или вызов библиотеки) в зависимости от размера скопированного блока и оптимизации процессора. Добавлена новая опция -minline-stringops-dynamic. С помощью этой опции строятся операции с неизвестным размером, так что небольшие блоки копируются по встроенному коду, а для больших блоков используется вызов библиотеки. Это приводит к более быстрому коду, чем -minline-all-stringops, когда реализация библиотеки способна использовать подсказки иерархии кеша. Эвристический выбор конкретного алгоритма может быть перезаписан с помощью стратегии -mstringop. Вновь добавляется также memset значений, отличных от 0.
Надеемся, что это объясняет, что пытаются сделать разработчики компилятора (даже если это для другой версии); -)