64-битная проблема производительности Linux с memset - PullRequest
12 голосов
/ 23 января 2012

Я отлаживаю приложение, которое работает немного медленнее, когда оно собрано как 64-битный исполняемый файл Linux ELF, чем как 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; я понимаю, что мне нужно сделать это дальше). Количество потраченного дополнительного времени может немного отличаться от одной системы к другой.

Что здесь происходит? Существует ли общеизвестная проблема, которую не может обнаружить мой Google-foo?

РЕДАКТИРОВАТЬ: Разборка (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
* +1034 * 64-битный: * +1035 *
.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® Core ™ TM i7-2600K @ 3,40 ГГц / VirtualBox
    (расхождение хуже на Xeon E5620 @ 2,40 ГГц / VMWare)

Ответы [ 3 ]

1 голос
/ 23 января 2012

Я могу подтвердить, что в моей не виртуализированной системе Mandriva Linux версия x86_64 немного (примерно на 7%) медленнее. В обоих случаях вызывается библиотечная функция memset() независимо от размера слова набора команд.

Случайный взгляд на код сборки обеих реализаций библиотеки показывает, что версия x86_64 значительно сложнее . Я предполагаю, что это главным образом связано с тем фактом, что 32-разрядная версия имеет дело только с 4 возможными случаями выравнивания, по сравнению с 8 возможными случаями выравнивания 64-разрядной версии. Также кажется, что цикл x86_64 memset() развернут более широко, возможно, из-за различных оптимизаций компилятора.

Другим фактором, который может объяснить более медленные операции, является увеличенная нагрузка ввода-вывода, связанная с использованием слова размером 64 бита. Как код, так и метаданные (указатели и т. Д.) Обычно становятся больше в 64-битных приложениях.

Кроме того, имейте в виду, что реализации библиотеки, включенные в большинство дистрибутивов, нацелены на любой процессор, который сопровождающие считают текущим наименьшим общим знаменателем для каждого семейства процессоров. Это может поставить 64-разрядные процессоры в невыгодное положение, поскольку набор 32-разрядных команд уже некоторое время стабилен.

1 голос
/ 23 января 2012

Я полагаю, что виртуализация является виновником: я провел несколько тестов самостоятельно (генерация случайных чисел в массовом порядке, последовательный поиск; также 64-битный) и обнаружил, что код работает в 2 раза медленнее в Linux в VirtualBox, чемизначально под окнами.Самое смешное, что код не выполняет ввод-вывод (за исключением простого printf время от времени, между таймингами) и использует мало памяти (все данные помещаются в кэш L1), поэтому можно подумать, что вы могли быисключить управление таблицей страниц и накладные расходы TLB.

Это действительно загадочно.Я заметил, что VirtualBox сообщает виртуальной машине, что инструкции SSE 4.1 и SSE 4.2 не поддерживаются, даже если их поддерживает ЦП, и программа, использующая их, прекрасно работает (!) В виртуальной машине.У меня нет времени на дальнейшее изучение проблемы, но вы ДЕЙСТВИТЕЛЬНО должны рассчитать время на реальной машине.К сожалению, моя программа не будет работать на 32 битах, поэтому я не смог проверить замедление в 32-битном режиме.

0 голосов
/ 23 января 2012

При компиляции вашего примера кода компилятор видит фиксированный размер блока (~ 8 МБ) и решает использовать версию библиотеки. Попробуйте код для гораздо меньших блоков (для memset'а всего на несколько байтов) - сравните разборку.

Хотя я не знаю, почему версия x64 медленнее. Я полагаю, в вашем коде измерения времени есть проблема.

Из журнала изменений gcc 4.3 :

Генерация кода перемещения блока (memcpy) и набора блоков (memset) была переписана. Теперь GCC может выбрать лучший алгоритм (цикл, развернутый цикл, инструкция с префиксом rep или вызов библиотеки) в зависимости от размера копируемого блока и оптимизируемого ЦП. Добавлена ​​новая опция -minline-stringops-dynamicically. С этой опцией строковые операции неизвестного размера расширяются так, что маленькие блоки копируются встроенным кодом, в то время как для больших блоков используется вызов библиотеки. Это приводит к более быстрому коду, чем -minline-all-stringops, когда реализация библиотеки способна использовать подсказки иерархии кэша. Эвристический выбор конкретного алгоритма может быть перезаписан с помощью -mstringop-стратегии. Недавно также указывается memset значений, отличных от 0.

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

...