- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
std::vector<double> WBuffer;
std::vector<double> CleanWBuffer;
std::vector<Color> PixelBuffer;
std::vector<Color> CleanPixelBuffer;
void Scene3D::ClearBuffers()
{
const size_t n = static_cast<size_t>(ScreenSize[0] * ScreenSize[1]);
memcpy(&*(WBuffer.begin()), &*(CleanWBuffer.begin()), n * sizeof(*(WBuffer.begin())));
memcpy(&*(PixelBuffer.begin()), &*(CleanPixelBuffer.begin()), n * sizeof(*(PixelBuffer.begin())));
}
Быстрая очистка буферов.
CleanWBuffer предварительно заполнен 0.0, CleanPixelBuffer предварительно заполнен нужным цветом.
Можно было воспользоваться std::fill, но оно работает в несколько раз дольше.
Пришлось так вот лезть в потроха std::vector. Доставляют подряд идущие & и *.
Иначе так было бы нельзя делать.
Да и не понятна причина использования здесь вектора...
Статический массив статичен, неизвестно какого размера делать.
Динамическую память выделять через new - потом неясно как размер менять если понадобится, лишний delete в деструкторе не забывать..., и вообще, мы в С++, на дворе 21 век, космические корабли...
Просто вообще говоря не пользовался ни malloc ни new[]. Предпочитаю работать с памятью через толстый слой абстракций типа векторов.
Выделить новый буфер при необходимости - да это проблема.
> лишний delete в деструкторе не забывать...
Smart Pointers позволяют забыть
> Доставляют подряд идущие & и *.
Это потому что memcpy() хочет адрес нормальный, а не итератор.
> sizeof(*(WBuffer.begin())));
нагляднее sizeof( WBuffer::value_type );
??
Color default_color = { ... };
PixelBuffer.assign(n, default_color);
В случае когда количество элементов не меняется, assign и fill эквивалентны.
Конструирование нового вектора (как предлагает ch) подозреваю помимо этого ещё и привело бы к выделению новой области памяти вместо того, чтобы просто очистить использованную ранее, хотя насколько это повлияло бы не знаю.
Объединяет все эти три метода то, что внутри STL будет проходить цикл, который будет заполнять вектор нужными значениями (0.0). Этот цикл довольно таки долог. Именно этого я и избежал используя memcpy. Как теперь выяснилось, учитывая специфику представления double в двоичном виде, можно использовать memset.
Это официальная фича std::vector: указатель на первый элемент есть указатель на непрерывный массив содержащий все элементы вектора.
Говно, только если например тип Color не есть POD (plain old data). Если это просто структура, то тогда все в порядке.
> Можно было воспользоваться std::fill, но оно работает в несколько раз дольше.
Что меня неимоверно бесит. STL и темплейты в прошлом обещали что позволят компиляторам кучи новых оптимизаций. Две пятилетки спустя, а воз и ныне там.
даже если так, может проще:
memset(&*(WBuffer.begin()), 0, n * sizeof(*(WBuffer.begin()))); // как раз 0 дабловский получится.
memset(&*(PixelBuffer.begin()), 0, n * sizeof(*(PixelBuffer.begin())));
тогда и 2-х лишних массивов не надо.
Можно пойти еще дальше - поэкспериментировать с написанием другой реализации memset - не через rep movsd - на каких-то системах может оказаться заметно быстрее, особенно если будет мало push и pop :)
PS. Не хватает проверки на нулевой размер вектора.
А 10% на очистку буферов (утилитарная операция) это много.
А вот то что дабловский 0.0 это когда все байты равны 0 - это подозрительное предположение.
{
double a=0;
char *b=&a;
for(int i=0; i<sizeof(double);i++) printf("%d\n", b[i]);
}
Вот навскидку ресурс: http://speleotrove.com/decimal/
Есть ли гарантия, что на всех аппаратных платформах, под которые есть C++ компиляторы этот стандарт имеет место быть?
Нет, думаю, такой гарантии нет:) Хотя... Я C++ не знаю, но там в стандарте написано, что поддерживается тип double, нет? Сказано там что-то про реализацию этого типа? Если сказано, то в компиляторах, соответствующих стандарту, должно прокатить. Если реализация 754 верная. Если...
Но изначально речь шла о том, как по-быстрому занулить дабл. Всеми нулями быстрее, чем нулями и одним 0x80:)
Объясните хоть чем это хуже 10-й строки
memcpy(&WBuffer[0], &CleanWBuffer[0], n * sizeof(WBuffer[0]));
Алгоритм для работы с памятью медленный? Непорядок! Процессор загружен во время очистки экрана? Вот черт, на рендер-то и времени глядишь не хватит!
И как обычно в комментариях дали достаточное количество идей по его улучшению.
"Алгоритм для работы с памятью медленный? Непорядок! Процессор загружен во время очистки экрана? Вот черт, на рендер-то и времени глядишь не хватит!"
К чему сарказм?
Проект был учебный, запустил профилировщик. Увидел что 10% времени уходит на очистку [с помощью std::fill]. Оставшиеся 90% размазаны по коду рендера. Прежде чем лезть и оптимизировать [более сложный] код рендера, можно заменить std::fill на memcpy (про то что 0.0 == 0x00000000 я тогда не знал к сожалению, иначе обошёлся бы memset) и сэкономить 10%.
WBuffer = vector<double>(n, 0);
Не вижу оснований считать что fill быстрее без профилирования. Таким образом говно началось с момента применения неадекватных инструментов. Если есть пустая копия, то оптимизация заключается в смене аргумента
WBuffer = vector<double>(CleanWBuffer);
Если оптимизировать дальше, возникает вопрос зачем тут вектор, ведь memset'ом работаем как с массивом...
Сарказм к тому, что все много пишут, а самое правильное решение, находящееся в учебнике для начинающих, игнорируют и минусуют.
Вон, Govnoeb чуть больше манула прочитал.
Почему самый обычный?
Использование конструктора - это самый обычный способ *создать* *новый* вектор заполненный нулями. Потому он и называется конструктором.
Нужно заполнить вектор нулями... переводим слово "заполнить" на английский... это будет fill... о, есть библиотечная функция std::fill!
Чем не обычно?
На ваше и Govnoeb-a решение ответил.
посмотрите формат хранения даблов: http://en.wikipedia.org/wiki/Binary64
0 записывать так можно.
у меня была аналогичная проблема при написании проекта на дельфи 6, а там очень плохо с оптимизацией кода компилятором.
более того, собственная реализация аналога memset ускорило еще процентов на 30.
Если хотите, пришлю код.
Например можно шаблонную специализуцию пихнуть для не стандартных дивайсов, в которой будет более медленная, но верная очистка.
А в Си вроде была функция realloc() (для malloc()/calloc()) .
во2 может функции отрисовки подсововать разные массивы для отрисовки, чем занулять один?
2) чем разные массивы лучше? занулять их всё равно придётся, но помимо этого придётся ещё и память много раз выделять.