Char [] в шестнадцатеричную строку упражнение

Ниже приведена моя текущая функция от 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: заметил, что дает неверные результаты

функция, как написано, больше не работает

hoyhoy 16.09.2008 11:35

вы можете инициализировать свой массив _hex2asciiU_value с помощью добавленного мной цикла for. Кроме того, я заметил, что в моем ответе есть недостаток, я указал длину указанного массива как 255 вместо 256.

hoyhoy 17.09.2008 00:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
2
17 825
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

Например, вместо умножения на 16 сделайте bitshift << 4

Также не используйте std::string, вместо этого просто создайте буфер в куче, а затем delete. Это будет более эффективно, чем уничтожение объекта, которое требуется из строки.

Компилятор должен сделать это за вас. Битовый сдвиг вместо умножения делает код менее читабельным.

Jeremy Ruten 16.09.2008 07:18

Не то чтобы в его коде отсутствовали другие битовые сдвиги ... И нет никакой гарантии, что его компилятор сделает это. И его конкретный вопрос был о том, чтобы сделать его более эффективным.

Brian R. Bondy 16.09.2008 07:20

Нет гарантии, что умножение быстрее сдвига.

nos 07.08.2009 16:51

Работайте с 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], в одной инструкции.

кажется сомнительным! Индекс в массиве является просто арифметика указателя!

Mitch Wheat 29.09.2008 07:54

Функция в том виде, в котором она показана, когда я пишу это, выдает неверный результат, даже если полностью указано значение _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> символов

Josh 17.09.2008 09:52

Если вы здесь одержимы скоростью, вы можете сделать следующее:

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

Итак, вы можете сделать следующее:

  1. Распакуйте четырехбитные значения в 8-битные значения с помощью умножения или аналогичной инструкции.
  2. Используйте pshufb, инструкцию SSSE3 (хотя только для Core2). Он принимает массив из 16 8-битных входных значений и перемешивает их на основе 16 8-битных индексов во втором векторе. Поскольку у вас есть только 16 возможных символов, это идеально подходит; входной массив - это вектор от 0 до F символов, а индексный массив - это ваш распакованный массив 4-битных значений.

Таким образом, в единственная инструкция вы будете выполнять 16 таблиц поиска за меньшее количество тактов, чем обычно требуется для выполнения всего одного (pshufb - это задержка в 1 такт на Penryn).

Итак, на вычислительных этапах:

  1. A B C D E F G H I J K L M N O P (64-битный вектор входных значений, «Вектор A») -> 0A 0B 0C 0D 0E 0F 0G 0H 0I 0J 0K 0L 0M 0N 0O 0P (128-битный вектор индексов, «Вектор B»). Самый простой способ - это, наверное, два 64-битных умножения.
  2. pshub [0123456789ABCDEF], вектор B

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

Что вы можете попробовать, так это развернуть цикл, делать более крупные шаги и вводить больше символов каждый раз в цикле, чтобы удалить часть накладных расходов цикла.

Больше байтов за раз должно работать отлично, вплоть до размера слова системы

Josh 17.09.2008 09:53

Постоянно получаю ~ 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));
}

Что вы использовали для построения этого графика?

0xC0DEFACE 03.05.2011 16:41

Вы выделили место для завершающего NUL, но никогда не назначали его.

Ben Voigt 24.05.2012 19:34

Эта функция сборки (основанная на моем предыдущем посте здесь, но мне пришлось немного изменить концепцию, чтобы заставить ее работать) обрабатывает 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!

d33tah 03.11.2013 04:06

Я предполагаю, что это 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"

Другие вопросы по теме