Как быстро заполнить память с помощью значения `int32_t`?
Есть ли функция (SSEx intrinsics в порядке), которая заполнит память указанным значением int32_t
? Например, когда это значение равно 0xAABBCC00
, память результатов должна выглядеть так:
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
AABBCC00AABBCC00AABBCC00AABBCC00AABBCC00
...
Я мог бы использовать std::fill
или простой for-loop, но он недостаточно быстр.
Изменение размера вектора, выполненного только один раз в начале программы, это не проблема. Узкое место заполняет память.
Упрощенный код:
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;
}
Ответы
Ответ 1
Спасибо всем за ваши ответы. Я проверил решение 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 и добавить для этого соответствующую обработку.
Ответ 2
Вот как бы я это сделал (пожалуйста, извините за 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;
}
}
Ответ 3
Я должен спросить: вы определенно профилировали std::fill
и показали, что это узкое место в производительности? Я предполагаю, что это будет реализовано довольно эффективно, так что компилятор может автоматически сгенерировать соответствующие инструкции (например, -march
on gcc).
Если это узкое место, все же возможно получить лучшую выгоду от алгоритмической редизайна (если это возможно), чтобы избежать установки так много памяти (видимо, снова и снова), так что теперь не имеет значения, какой механизм заполнения использовать.
Ответ 4
Рассматривали ли вы использование
vector<int32_t> myVector;
myVector.reserve( sizeIWant );
а затем используйте std:: fill? Или, возможно, конструктор a std::vector
, который принимает в качестве аргумента количество сохраненных элементов и значение для их инициализации в?
Ответ 5
Не совсем уверен, как вы устанавливаете 4 байта в строке, но если вы хотите снова заполнить память одним байтом заново, вы можете использовать memset
.
void * memset ( void * ptr, int value, size_t num );
Заполнить блок памяти
Устанавливает первые num байты блока памяти, на которые указывает ptr
, на указанное значение (интерпретируется как unsigned char
).
Ответ 6
Предполагая, что у вас есть ограниченное количество значений в вашем фоновом параметре (или даже лучше, только на), возможно, вам стоит попытаться выделить статический вектор и просто использовать 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;
}
Ответ 7
Я только что протестировал 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 байт за раз.
Ответ 8
vs2013 и vs2015 могут оптимизировать простой для цикла цикл в инструкцию 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;
}
}
BTW. Чтобы компилятор выполнял оптимизацию, к буферу должен обращаться оператор подстроки.
Это не будет работать на gcc и clang. Они оба скомпилируют код в условный цикл перехода. Он работает так медленно, как исходный std::fill
. И хотя wchar_t
является 32-битным, wmemset
не имеет сборки, как нравится memset
. Поэтому вам нужно написать код сборки для оптимизации.
Ответ 9
Это может быть немного не переносимым, но вы можете использовать дублирующуюся копию памяти.
Заполните первые четыре байта с помощью шаблона, который вы хотите, и используйте memcpy().
int32* p = (int32*) malloc( size );
*p = 1234;
memcpy( p + 4, p, size - 4 );
Не думайте, что вы можете ускориться быстрее