Ниже приведена моя текущая функция от char * до шестнадцатеричной строки. Я написал это как упражнение по манипуляции с битами. На AMD Athlon MP 2800+ требуется ~ 7 мсек, чтобы сделать шестимиллионный массив размером 10 миллионов байт. Есть ли какой-то трюк или другой способ, который мне не хватает?
Как я могу сделать это быстрее?
Скомпилировано с -O3 в g ++
static const char _hex2asciiU_value[256][2] =
{ {'0','0'}, {'0','1'}, /* snip..., */ {'F','E'},{'F','F'} };
std::string char_to_hex( const unsigned char* _pArray, unsigned int _len )
{
std::string str;
str.resize(_len*2);
char* pszHex = &str[0];
const unsigned char* pEnd = _pArray + _len;
clock_t stick, etick;
stick = clock();
for( const unsigned char* pChar = _pArray; pChar != pEnd; pChar++, pszHex += 2 ) {
pszHex[0] = _hex2asciiU_value[*pChar][0];
pszHex[1] = _hex2asciiU_value[*pChar][1];
}
etick = clock();
std::cout << "ticks to hexify " << etick - stick << std::endl;
return str;
}
Обновления
Добавлен код времени
Брайан Р. Бонди: заменить std :: string буфером, выделенным в куче, и изменить с * 16 на ofs << 4 - однако буфер, выделенный в куче, кажется, замедляет его? - результат ~ 11мс
Антти Сюкари: заменить внутренний цикл на
int upper = *pChar >> 4;
int lower = *pChar & 0x0f;
pszHex[0] = pHex[upper];
pszHex[1] = pHex[lower];
результат ~ 8 мс
Роберт: замените _hex2asciiU_value полной таблицей из 256 записей, жертвуя пространством памяти, но результатом ~ 7 мс!
HoyHoy: заметил, что дает неверные результаты
вы можете инициализировать свой массив _hex2asciiU_value с помощью добавленного мной цикла for. Кроме того, я заметил, что в моем ответе есть недостаток, я указал длину указанного массива как 255 вместо 256.





Например, вместо умножения на 16 сделайте bitshift << 4
Также не используйте std::string, вместо этого просто создайте буфер в куче, а затем delete. Это будет более эффективно, чем уничтожение объекта, которое требуется из строки.
Компилятор должен сделать это за вас. Битовый сдвиг вместо умножения делает код менее читабельным.
Не то чтобы в его коде отсутствовали другие битовые сдвиги ... И нет никакой гарантии, что его компилятор сделает это. И его конкретный вопрос был о том, чтобы сделать его более эффективным.
Нет гарантии, что умножение быстрее сдвига.
Работайте с 32 битами за раз (4 символа), затем при необходимости обработайте хвост. Когда я выполнял это упражнение с кодировкой URL-адресов, полный поиск в таблице для каждого символа был немного быстрее, чем логические конструкции, поэтому вы можете проверить это в контексте, а также принять во внимание проблемы с кешированием.
не будет иметь большого значения ... * pChar- (ofs * 16) можно сделать с помощью [* pCHar & 0x0F]
Это моя версия, которая, в отличие от версии OP, не предполагает, что std::basic_string имеет данные в непрерывном регионе:
#include <string>
using std::string;
static char const* digits("0123456789ABCDEF");
string
tohex(string const& data)
{
string result(data.size() * 2, 0);
string::iterator ptr(result.begin());
for (string::const_iterator cur(data.begin()), end(data.end()); cur != end; ++cur) {
unsigned char c(*cur);
*ptr++ = digits[c >> 4];
*ptr++ = digits[c & 15];
}
return result;
}
Убедитесь, что оптимизация вашего компилятора включена на самый высокий рабочий уровень.
Вы знаете, что в gcc используются флаги от «-O1» до «-03».
Ценой большего объема памяти вы можете создать полную таблицу шестнадцатеричных кодов из 256 записей:
static const char _hex2asciiU_value[256][2] =
{ {'0','0'}, {'0','1'}, /* ..., */ {'F','E'},{'F','F'} };
Затем направьте индекс в таблицу, без необходимости возиться с битами.
const char *pHexVal = pHex[*pChar];
pszHex[0] = pHexVal[0];
pszHex[1] = pHexVal[1];
Изменение
ofs = *pChar >> 4;
pszHex[0] = pHex[ofs];
pszHex[1] = pHex[*pChar-(ofs*16)];
к
int upper = *pChar >> 4;
int lower = *pChar & 0x0f;
pszHex[0] = pHex[upper];
pszHex[1] = pHex[lower];
приводит к ускорению примерно на 5%.
Запись результата по два байта за раз, как предлагает Роберт, приводит к ускорению примерно на 18%. Код изменится на:
_result.resize(_len*2);
short* pszHex = (short*) &_result[0];
const unsigned char* pEnd = _pArray + _len;
const char* pHex = _hex2asciiU_value;
for(const unsigned char* pChar = _pArray;
pChar != pEnd;
pChar++, ++pszHex )
{
*pszHex = bytes_to_chars[*pChar];
}
Требуемая инициализация:
short short_table[256];
for (int i = 0; i < 256; ++i)
{
char* pc = (char*) &short_table[i];
pc[0] = _hex2asciiU_value[i >> 4];
pc[1] = _hex2asciiU_value[i & 0x0f];
}
Выполнение этого 2 байта за раз или 4 байта за раз, вероятно, приведет к еще большему ускорению, как указано в Аллан Винд, но затем это становится сложнее, когда вам приходится иметь дело с нечетными символами.
Если вы любите приключения, вы можете попробовать адаптировать Устройство Даффа для этого.
Результаты получены на процессоре Intel Core Duo 2 и gcc -O3.
Всегда измеряйте, что вы действительно получаете более быстрые результаты - пессимизация, притворяющаяся оптимизацией, менее чем бесполезна.
Всегда проверяйте, что вы получаете правильные результаты - ошибка, претендующая на оптимизацию, совершенно опасна.
А всегда имей в виду - это компромисс между скоростью и удобочитаемостью - жизнь слишком коротка, чтобы кто-либо мог поддерживать нечитаемый код.
(Обязательная ссылка для кодирования для жестокий психопат, который знает, где вы живете.)
Я обнаружил, что использование индекса в массиве, а не указателя, может ускорить процесс. Все зависит от того, как ваш компилятор выбирает оптимизацию. Ключ в том, что у процессора есть инструкции для выполнения сложных вещей, таких как [i * 2 + 1], в одной инструкции.
кажется сомнительным! Индекс в массиве является просто арифметика указателя!
Функция в том виде, в котором она показана, когда я пишу это, выдает неверный результат, даже если полностью указано значение _hex2asciiU_value. Следующий код работает, и на моем Macbook Pro с тактовой частотой 2,33 ГГц выполняется примерно 1,9 секунды для 200000000 миллионов символов.
#include <iostream>
using namespace std;
static const size_t _h2alen = 256;
static char _hex2asciiU_value[_h2alen][3];
string char_to_hex( const unsigned char* _pArray, unsigned int _len )
{
string str;
str.resize(_len*2);
char* pszHex = &str[0];
const unsigned char* pEnd = _pArray + _len;
const char* pHex = _hex2asciiU_value[0];
for( const unsigned char* pChar = _pArray; pChar != pEnd; pChar++, pszHex += 2 ) {
pszHex[0] = _hex2asciiU_value[*pChar][0];
pszHex[1] = _hex2asciiU_value[*pChar][1];
}
return str;
}
int main() {
for(int i=0; i<_h2alen; i++) {
snprintf(_hex2asciiU_value[i], 3,"%02X", i);
}
size_t len = 200000000;
char* a = new char[len];
string t1;
string t2;
clock_t start;
srand(time(NULL));
for(int i=0; i<len; i++) a[i] = rand()&0xFF;
start = clock();
t1=char_to_hex((const unsigned char*)a, len);
cout << "char_to_hex conversion took ---> " << (clock() - start)/(double)CLOCKS_PER_SEC << " seconds\n";
}
> 200 000 000 <s> миллионов </s> символов
Если вы здесь одержимы скоростью, вы можете сделать следующее:
Каждый символ представляет собой один байт, представляющий два шестнадцатеричных значения. Таким образом, каждый символ действительно представляет собой два четырехбитных значения.
Итак, вы можете сделать следующее:
Таким образом, в единственная инструкция вы будете выполнять 16 таблиц поиска за меньшее количество тактов, чем обычно требуется для выполнения всего одного (pshufb - это задержка в 1 такт на Penryn).
Итак, на вычислительных этапах:
Я не уверен, что делать больше байтов за раз будет лучше ... вы, вероятно, просто получите кучу промахов кеша и значительно замедлит его.
Что вы можете попробовать, так это развернуть цикл, делать более крупные шаги и вводить больше символов каждый раз в цикле, чтобы удалить часть накладных расходов цикла.
Больше байтов за раз должно работать отлично, вплоть до размера слова системы
Постоянно получаю ~ 4 мс на моем Athlon 64 4200+ (~ 7 мс с исходным кодом)
for( const unsigned char* pChar = _pArray; pChar != pEnd; pChar++) {
const char* pchars = _hex2asciiU_value[*pChar];
*pszHex++ = *pchars++;
*pszHex++ = *pchars;
}
Более быстрое внедрение C
Это работает почти в 3 раза быстрее, чем реализация на C++. Не уверен, почему, потому что это очень похоже. Для последней опубликованной мной реализации C++ потребовалось 6,8 секунды для прохождения 200000000 символьного массива. Реализация заняла всего 2,2 секунды.
#include <stdio.h>
#include <stdlib.h>
char* char_to_hex(const unsigned char* p_array,
unsigned int p_array_len,
char** hex2ascii)
{
unsigned char* str = malloc(p_array_len*2+1);
const unsigned char* p_end = p_array + p_array_len;
size_t pos=0;
const unsigned char* p;
for( p = p_array; p != p_end; p++, pos+=2 ) {
str[pos] = hex2ascii[*p][0];
str[pos+1] = hex2ascii[*p][1];
}
return (char*)str;
}
int main()
{
size_t hex2ascii_len = 256;
char** hex2ascii;
int i;
hex2ascii = malloc(hex2ascii_len*sizeof(char*));
for(i=0; i<hex2ascii_len; i++) {
hex2ascii[i] = malloc(3*sizeof(char));
snprintf(hex2ascii[i], 3,"%02X", i);
}
size_t len = 8;
const unsigned char a[] = "DO NOT WANT";
printf("%s\n", char_to_hex((const unsigned char*)a, len, (char**)hex2ascii));
}

Что вы использовали для построения этого графика?
Вы выделили место для завершающего NUL, но никогда не назначали его.
Эта функция сборки (основанная на моем предыдущем посте здесь, но мне пришлось немного изменить концепцию, чтобы заставить ее работать) обрабатывает 3,3 миллиарда входных символов в секунду (6,6 миллиарда выходных символов) на одном ядре Core 2 Conroe 3Ghz. Пенрин, вероятно, быстрее.
%include "x86inc.asm"
SECTION_RODATA
pb_f0: times 16 db 0xf0
pb_0f: times 16 db 0x0f
pb_hex: db 48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70
SECTION .text
; int convert_string_to_hex( char *input, char *output, int len )
cglobal _convert_string_to_hex,3,3
movdqa xmm6, [pb_f0 GLOBAL]
movdqa xmm7, [pb_0f GLOBAL]
.loop:
movdqa xmm5, [pb_hex GLOBAL]
movdqa xmm4, [pb_hex GLOBAL]
movq xmm0, [r0+r2-8]
movq xmm2, [r0+r2-16]
movq xmm1, xmm0
movq xmm3, xmm2
pand xmm0, xmm6 ;high bits
pand xmm2, xmm6
psrlq xmm0, 4
psrlq xmm2, 4
pand xmm1, xmm7 ;low bits
pand xmm3, xmm7
punpcklbw xmm0, xmm1
punpcklbw xmm2, xmm3
pshufb xmm4, xmm0
pshufb xmm5, xmm2
movdqa [r1+r2*2-16], xmm4
movdqa [r1+r2*2-32], xmm5
sub r2, 16
jg .loop
REP_RET
Обратите внимание, что он использует синтаксис сборки x264, что делает его более переносимым (32-разрядный или 64-разрядный и т. д.). Преобразовать это в синтаксис по вашему выбору тривиально: r0, r1, r2 - три аргумента функций в регистрах. Это немного похоже на псевдокод. Или вы можете просто получить common / x86 / x86inc.asm из дерева x264 и включить его, чтобы запустить его изначально.
P.S. Stack Overflow, я ошибаюсь, что трачу время на такую тривиальную вещь? Или это круто?
Ого. Никогда не знал о x264!
Я предполагаю, что это Windows + IA32.
Попробуйте использовать short int вместо двух шестнадцатеричных букв.
short int hex_table[256] = {'0'*256+'0', '1'*256+'0', '2'*256+'0', ..., 'E'*256+'F', 'F'*256+'F'};
unsigned short int* pszHex = &str[0];
stick = clock();
for (const unsigned char* pChar = _pArray; pChar != pEnd; pChar++)
*pszHex++ = hex_table[*pChar];
etick = clock();
У меня работает с unsigned char:
unsigned char c1 = byteVal >> 4;
unsigned char c2 = byteVal & 0x0f;
c1 += c1 <= 9 ? '0' : ('a' - 10);
c2 += c2 <= 9 ? '0' : ('a' - 10);
std::string sHex(" ");
sHex[0] = c1 ;
sHex[1] = c2 ;
//sHex - contain what we need. For example "0f"
функция, как написано, больше не работает