Как быстро заполнить память значением `int32_t`? - PullRequest
11 голосов
/ 09 июля 2010

Есть ли функция (встроенная функция SSEx в порядке), которая заполнит память указанным значением int32_t? Например, когда это значение равно 0xAABBCC00, память результатов должна выглядеть следующим образом:

AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
...

Я мог бы использовать std::fill или простой цикл for, но он недостаточно быстр.


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

Упрощенный код:

struct X
{
  typedef std::vector<int32_t> int_vec_t;
  int_vec_t buffer;

  X() : buffer( 5000000 ) { /* some more action */ }
  ~X() { /* some code here */ }

  // the following function is called 25 times per second
  const int_vec_t& process( int32_t background, const SOME_DATA& data );
};

const X::int_vec_t& X::process( int32_t background, const SOME_DATA& data )
{
    // the following one string takes 30% of total time of #process function
    std::fill( buffer.begin(), buffer.end(), background );

    // some processing
    // ...

    return buffer;
}

Ответы [ 9 ]

9 голосов
/ 09 июля 2010

Вот как бы я это сделал (прошу прощения у Microsoft):

VOID FillInt32(__out PLONG M, __in LONG Fill, __in ULONG Count)
{
    __m128i f;

    // Fix mis-alignment.
    if ((ULONG_PTR)M & 0xf)
    {
        switch ((ULONG_PTR)M & 0xf)
        {
            case 0x4: if (Count >= 1) { *M++ = Fill; Count--; }
            case 0x8: if (Count >= 1) { *M++ = Fill; Count--; }
            case 0xc: if (Count >= 1) { *M++ = Fill; Count--; }
        }
    }

    f.m128i_i32[0] = Fill;
    f.m128i_i32[1] = Fill;
    f.m128i_i32[2] = Fill;
    f.m128i_i32[3] = Fill;

    while (Count >= 4)
    {
        _mm_store_si128((__m128i *)M, f);
        M += 4;
        Count -= 4;
    }

    // Fill remaining LONGs.
    switch (Count & 0x3)
    {
        case 0x3: *M++ = Fill;
        case 0x2: *M++ = Fill;
        case 0x1: *M++ = Fill;
    }
}
6 голосов
/ 09 июля 2010

Я должен спросить: Вы определенно профилировали std::fill и показали, что это узкое место производительности?Я предполагаю, что это будет реализовано довольно эффективным способом, так что компилятор может автоматически генерировать соответствующие инструкции (например, -march на gcc).

Если это узкое место, это все еще возможночтобы получить максимальную выгоду от алгоритмического перепроектирования (если это возможно), чтобы избежать установки такого большого количества памяти (очевидно, снова и снова), чтобы больше не имело значения, какой механизм заполнения вы используете.

4 голосов
/ 19 июля 2010

Спасибо всем за ваши ответы. Я проверил решение wj32 , но оно показывает время, очень похожее на std::fill. Мое текущее решение работает в 4 раза быстрее (в Visual Studio 2008), чем std::fill с помощью функции memcpy:

 // fill the first quarter by the usual way
 std::fill(buffer.begin(), buffer.begin() + buffer.size()/4, background);
 // copy the first quarter to the second (very fast)
 memcpy(&buffer[buffer.size()/4], &buffer[0], buffer.size()/4*sizeof(background));
 // copy the first half to the second (very fast)
 memcpy(&buffer[buffer.size()/2], &buffer[0], buffer.size()/2*sizeof(background));

В производственном коде нужно добавить проверку, если buffer.size() делится на 4, и добавить соответствующую обработку для этого.

3 голосов
/ 09 июля 2010

Рассматривали ли вы использование

vector<int32_t> myVector;
myVector.reserve( sizeIWant );

и затем использовать std :: fill? Или, возможно, конструктор std::vector, который принимает в качестве аргумента количество элементов и значение для их инициализации?

0 голосов
/ 29 октября 2015

vs2013 и vs2015 могут оптимизировать простой цикл for до rep stos инструкции. Это самый быстрый способ заполнить буфер. Вы можете указать std::fill для вашего типа следующим образом:

namespace std {
    inline void fill(vector<int>::iterator first, vector<int>::iterator last, int value){
        for (size_t i = 0; i < last - first; i++)
            first[i] = value;
    }
}

КСТАТИ. Чтобы компилятор выполнял оптимизацию, доступ к буферу должен выполняться оператором нижнего индекса.

Это не будет работать на gcc и clang. Они оба скомпилируют код в цикл условного перехода. Он работает так же медленно, как оригинал std::fill. И хотя wchar_t является 32-битным, у wmemset нет ассемблера, как у memset. Таким образом, вы должны написать ассемблерный код для оптимизации.

0 голосов
/ 09 июля 2010

Я только что протестировал std :: fill с g ++ с полной оптимизацией (SSE и т.д .. включены):

#include <algorithm>
#include <inttypes.h>

int32_t a[5000000];

int main(int argc,char *argv[])
{
    std::fill(a,a+5000000,0xAABBCC00);
    return a[3];
}

и внутренний цикл выглядел так:

L2:
    movdqa  %xmm0, -16(%eax)
    addl    $16, %eax
    cmpl    %edx, %eax
    jne L2

Похоже0xAABBCC00 x 4 был загружен в xmm0 и перемещается по 16 байт за раз.

0 голосов
/ 09 июля 2010

Это может быть немного непереносимо, но вы можете использовать дублирующуюся копию памяти.Заполните первые четыре байта нужным вам шаблоном и используйте memcpy ().

int32* p = (int32*) malloc( size );
*p = 1234;
memcpy( p + 4, p, size - 4 );

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

0 голосов
/ 09 июля 2010

Если у вас есть ограниченное количество значений в фоновом параметре (или, что еще лучше, только при включенном), возможно, вам следует попытаться выделить статический вектор и просто использовать memcpy.

const int32_t sBackground = 1234;
static vector <int32_t> sInitalizedBuffer(n, sBackground);

    const X::int_vec_t& X::process( const SOME_DATA& data )
    {
        // the following one string takes 30% of total time of #process function
        std::memcpy( (void*) data[0], (void*) sInitalizedBuffer[0], n * sizeof(sBackground));

        // some processing
        // ...

        return buffer;
    }
0 голосов
/ 09 июля 2010

Не совсем уверен, как установить 4 байта в строке, но если вы хотите заполнить память одним байтом за один раз, вы можете использовать memset.

void * memset ( void * ptr, int value, size_t num );

Заполнить блок памяти

Устанавливает первые n байтов блока памяти, указанного ptr, в указанное значение (интерпретируется как unsigned char).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...