Подтвердить что ты не робот

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 )
4b9b3361

Ответ 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.

Надеемся, что это объясняет, что пытаются сделать разработчики компилятора (даже если это для другой версии); -)