1. C++ / Говнокод #4604

    +163

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 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. Доставляют подряд идущие & и *.

    Запостил: burdakovd, 12 Ноября 2010

    Комментарии (39) RSS

    • Ах да, Color не содержит указателей, не имеет деструктора, и использует конструктор копирования и оператор присваивания по умолчанию.

      Иначе так было бы нельзя делать.
      Ответить
    • *(WBuffer.begin()) == WBuffer.front()
      Да и не понятна причина использования здесь вектора...
      Ответить
      • Ну а что ещё.

        Статический массив статичен, неизвестно какого размера делать.
        Динамическую память выделять через new - потом неясно как размер менять если понадобится, лишний delete в деструкторе не забывать..., и вообще, мы в С++, на дворе 21 век, космические корабли...
        Ответить
        • Я бы не советовал смешивать malloc и delete :))
          Ответить
          • да, я уже поправил в комментарии s/malloc/new/
            Просто вообще говоря не пользовался ни malloc ни new[]. Предпочитаю работать с памятью через толстый слой абстракций типа векторов.
            Ответить
        • > потом неясно как размер менять если понадобится
          Выделить новый буфер при необходимости - да это проблема.
          > лишний delete в деструкторе не забывать...
          Smart Pointers позволяют забыть
          Ответить
        • ...космические корабли бороздят просторы Тихого океана
          Ответить
    • vector видимо затем, что array нету. Или вам приходилось размер менять буферов?
      > Доставляют подряд идущие & и *.
      Это потому что memcpy() хочет адрес нормальный, а не итератор.
      > sizeof(*(WBuffer.begin())));
      нагляднее sizeof( WBuffer::value_type );
      Ответить
    • WBuffer = vector<double>(n, CleanWBuffer);
      ??
      Ответить
      • WBuffer.assign(n, 0);

        Color default_color = { ... };
        PixelBuffer.assign(n, default_color);
        Ответить
        • Не вижу особой разницы между std::assign, std::fill, и конструированием каждый раз нового вектора из чистого.

          В случае когда количество элементов не меняется, assign и fill эквивалентны.
          Конструирование нового вектора (как предлагает ch) подозреваю помимо этого ещё и привело бы к выделению новой области памяти вместо того, чтобы просто очистить использованную ранее, хотя насколько это повлияло бы не знаю.

          Объединяет все эти три метода то, что внутри STL будет проходить цикл, который будет заполнять вектор нужными значениями (0.0). Этот цикл довольно таки долог. Именно этого я и избежал используя memcpy. Как теперь выяснилось, учитывая специфику представления double в двоичном виде, можно использовать memset.
          Ответить
    • > Пришлось так вот лезть в потроха std::vector

      Это официальная фича std::vector: указатель на первый элемент есть указатель на непрерывный массив содержащий все элементы вектора.

      Говно, только если например тип Color не есть POD (plain old data). Если это просто структура, то тогда все в порядке.

      > Можно было воспользоваться std::fill, но оно работает в несколько раз дольше.

      Что меня неимоверно бесит. STL и темплейты в прошлом обещали что позволят компиляторам кучи новых оптимизаций. Две пятилетки спустя, а воз и ныне там.
      Ответить
      • говно, в том, что завтра тех. требования могут изменится и Color станет QColor
        Ответить
        • QColor из Qt? это как раз не проблема так как в Qt уже есть оптимальные примитивы для работы с графикой в памяти - QImage, QPixmap и т.д.
          Ответить
    • так критично быстродействие, что "Можно было воспользоваться std::fill, но оно работает в несколько раз дольше." ?
      даже если так, может проще:
      memset(&*(WBuffer.begin()), 0, n * sizeof(*(WBuffer.begin()))); // как раз 0 дабловский получится.
      memset(&*(PixelBuffer.begin()), 0, n * sizeof(*(PixelBuffer.begin())));
      тогда и 2-х лишних массивов не надо.
      Можно пойти еще дальше - поэкспериментировать с написанием другой реализации memset - не через rep movsd - на каких-то системах может оказаться заметно быстрее, особенно если будет мало push и pop :)
      PS. Не хватает проверки на нулевой размер вектора.
      Ответить
      • Ну когда я использовал std::fill этот код тратил около 10% процессорного времени.

        А 10% на очистку буферов (утилитарная операция) это много.

        А вот то что дабловский 0.0 это когда все байты равны 0 - это подозрительное предположение.
        Ответить
        • Блин, ну шо за люди. Ну лень стандарт читать, так

          {
          double a=0;
          char *b=&a;

          for(int i=0; i<sizeof(double);i++) printf("%d\n", b[i]);
          }
          Ответить
          • Было бы не плохо ссылочку на стандарт.
            Ответить
            • IEEE 754 же!

              Вот навскидку ресурс: http://speleotrove.com/decimal/
              Ответить
              • А это )) Я что-то подумал про С++ стандарт.
                Есть ли гарантия, что на всех аппаратных платформах, под которые есть C++ компиляторы этот стандарт имеет место быть?
                Ответить
                • Вот и я удивился, что это, absolut, да 754 не знает. Нонсенс.

                  Нет, думаю, такой гарантии нет:) Хотя... Я C++ не знаю, но там в стандарте написано, что поддерживается тип double, нет? Сказано там что-то про реализацию этого типа? Если сказано, то в компиляторах, соответствующих стандарту, должно прокатить. Если реализация 754 верная. Если...
                  Ответить
          • а как вам такой ноль ?
            double minusZero( )
            {
            	double a;
            	char *b=(char*)&a;
            
            	b[0] = 0x80;
            	for( int i=1; i<sizeof(double);i++)
            	{ 
            		b[i] = 0;
            	}
            
            	return a;
            }
            Ответить
            • Намальный ноль, чо! Стандартный:)
              Но изначально речь шла о том, как по-быстрому занулить дабл. Всеми нулями быстрее, чем нулями и одним 0x80:)
              Ответить
              • отсюда мораль : не надо лазать по битам, и надеяться на них тоже (в большинстве случаев).
                Ответить
            • Да, я тоже уже прочитал http://en.wikipedia.org/wiki/Double_precision_floating-point_format =)
              Ответить
        • Все такие умные, stl такой неоптимизированный.
          Объясните хоть чем это хуже 10-й строки
          memcpy(&WBuffer[0], &CleanWBuffer[0], n * sizeof(WBuffer[0]));
          Алгоритм для работы с памятью медленный? Непорядок! Процессор загружен во время очистки экрана? Вот черт, на рендер-то и времени глядишь не хватит!
          Ответить
          • Да ничем не хуже. В конце концов раз я поместил этот код сюда, значит я сам понимаю что он подозрителен.

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

            "Алгоритм для работы с памятью медленный? Непорядок! Процессор загружен во время очистки экрана? Вот черт, на рендер-то и времени глядишь не хватит!"
            К чему сарказм?
            Проект был учебный, запустил профилировщик. Увидел что 10% времени уходит на очистку [с помощью std::fill]. Оставшиеся 90% размазаны по коду рендера. Прежде чем лезть и оптимизировать [более сложный] код рендера, можно заменить std::fill на memcpy (про то что 0.0 == 0x00000000 я тогда не знал к сожалению, иначе обошёлся бы memset) и сэкономить 10%.
            Ответить
            • В таком случае вообще не понятно откуда fill взялся, ведь самый... обычный способ забить вектор нулями
              WBuffer = vector<double>(n, 0);
              Не вижу оснований считать что fill быстрее без профилирования. Таким образом говно началось с момента применения неадекватных инструментов. Если есть пустая копия, то оптимизация заключается в смене аргумента
              WBuffer = vector<double>(CleanWBuffer);
              Если оптимизировать дальше, возникает вопрос зачем тут вектор, ведь memset'ом работаем как с массивом...
              Сарказм к тому, что все много пишут, а самое правильное решение, находящееся в учебнике для начинающих, игнорируют и минусуют.
              Вон, Govnoeb чуть больше манула прочитал.
              Ответить
              • >> ведь самый... обычный способ забить вектор нулями WBuffer = vector<double>(n, 0);

                Почему самый обычный?
                Использование конструктора - это самый обычный способ *создать* *новый* вектор заполненный нулями. Потому он и называется конструктором.

                Нужно заполнить вектор нулями... переводим слово "заполнить" на английский... это будет fill... о, есть библиотечная функция std::fill!
                Чем не обычно?


                На ваше и Govnoeb-a решение ответил.
                Ответить
        • да, 10% на подобную операцию - безумно много.
          посмотрите формат хранения даблов: http://en.wikipedia.org/wiki/Binary64
          0 записывать так можно.
          у меня была аналогичная проблема при написании проекта на дельфи 6, а там очень плохо с оптимизацией кода компилятором.
          более того, собственная реализация аналога memset ускорило еще процентов на 30.
          Если хотите, пришлю код.
          Ответить
          • После использования memcpy (ну или memset) этот фрагмент кода стал тратить исчезающе малое процессорное время, поэтому дальше его оптимизировать уже нет смысла.
            Ответить
        • Числа с плавающей точкой можно очищать, согласно стандарта представления этих чисел в х87 и выше, memset'ом. Гарантирую. Инфа 100%. Не тратьте время. Не читайте.
          Ответить
          • Если код нужно переносить куда-нибудь кроме стандартных персооналок, то лучше так не делать.

            Например можно шаблонную специализуцию пихнуть для не стандартных дивайсов, в которой будет более медленная, но верная очистка.
            Ответить
    • Изменять размер массива без вектора можно уже упомянутым сохранение>удаление>новый массив>вставка нужного.
      А в Си вроде была функция realloc() (для malloc()/calloc()) .
      Ответить
    • во1 что будет если размер экрана увеличится?

      во2 может функции отрисовки подсововать разные массивы для отрисовки, чем занулять один?
      Ответить
      • 1) Изменение размера экрана не было предусмотрено.
        2) чем разные массивы лучше? занулять их всё равно придётся, но помимо этого придётся ещё и память много раз выделять.
        Ответить
        • я вот о чем:
          namespace global {
          std::vector<double> WBuffer;
          std::vector<double>DefaultWBuffer;
          std::vector<Color> PixelBuffer;
          std::vector<Color> DefaultPixelBuffer;
          }
          
          void Scene3D::draw(const std::vector<Color> &data = PixelBuffer) { ... }
          void Scene3D::clear() { draw_scene(DefaultPixelBuffer); }
          Ответить
    • Злая тема.
      Ответить

    Добавить комментарий