Войти в систему

Home
    - Создать дневник
    - Написать в дневник
       - Подробный режим

LJ.Rossia.org
    - Новости сайта
    - Общие настройки
    - Sitemap
    - Оплата
    - ljr-fif

Редактировать...
    - Настройки
    - Список друзей
    - Дневник
    - Картинки
    - Пароль
    - Вид дневника

Сообщества

Настроить S2

Помощь
    - Забыли пароль?
    - FAQ
    - Тех. поддержка



Пишет kouzdra ([info]kouzdra)
@ 2009-06-02 14:34:00


Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:Компутерщина

О пресловутой "эффективности" С++:
К этой вот табличке (отсюда):

Я интереса ради сделал то, что советовали в комментах:

Написал вариант с кучей - с тремя вариантами - с auto_ptr, с shared_ptr "по-минимуму" и с shared-же ptr "по максимуму" - результат получился ожидаемый:

Time=2.19 (калибровочный исходный тест)
Time=41.44 (heap+auto_ptr)
Time=87.41 (heap+shared_ptr/min)
Time=83.87 (heap+shared_ptr/max)

Для калибровки еще Жаба:
Time=2.459 (inline вариант)
Time=9.026 (с классом complex)

Ну и OCaml (варианты из исходной статьи):
Time: 6.73479
Time: 8.0783
Time: 79.8885

И Haskell:

16.699

То есть мораль смешная - С++ ведет себя хорошо ровно до того момента, пока не начинается работа с кучей - и с этого момента он превращается в полную жопу даже по эффективности, даже по сравнению с O'Caml, у которого с плавающей арифметикой очень плохо, а объектность малоэффективна (там все методы виртуальные, причем диспатчатся они очень хитро, по причине структурного, а не именного subtyping'a
у объектов - вариант того, как такое надо писать на ML, если уж нужен офигенный полиморфизм - см. update).

Это, не говоря уж о потенциальной глючности и сложности кодирования плюсовой кучи.

Упреждая стандартный вопрос "зачем здесь куча" - отвечаю - здесь - ни зачем, но как только она понадобится - С++ начнет проигрывать всему, чему только можно (нет - есть конечно всякие аллокаторы, пулы и прочие финты ушами, которые более или менее вытянут
перформанс, но это уже гемморой)


import Complex

norm2 (x :+ y) = x*x+y*y

iters2 max_iter xc yc =
    let c = xc :+ yc in
    let aux count z = 
            if count >= max_iter 
            then max_iter 
            else
                if norm2 z >= 4.0 
                then count 
                else aux (count+1) (z * z + c)
    in
      aux 0 c

resolution = 5000
max_val = resolution / 2 
min_val = - max_val 
mul = 2 / max_val 
main = print (sum [iters2 100 (i * mul) (j * mul) | 
      i <- [min_val..max_val], j <- [min_val..max_val]])


Upd: И еще вариант на O'Caml с максимальным полиморфизмом,
но без ОО-жопы - в ML-style - с параметризоваными модулями - работает 17 секунд:

module type COMPL = 
  sig 
    type t
    type elem
    val to_float: elem -> float
    val of_float: float -> elem
    val make : re:elem -> im:elem -> t
    val add : t -> t -> t
    val mul : t -> t -> t
    val norm2 : t -> elem
  end

module Mand  (COMPL: COMPL) = struct

  let iters max_iter xc yc = 
    let c = COMPL.make xc yc in
    let rec aux count z = 
      if count >= max_iter then max_iter else begin
	if COMPL.norm2 z >= COMPL.of_float 4.0 then count else begin
          aux (count+1) (COMPL.add (COMPL.mul z z) c)
	end
      end in
    aux 0 c
end

module MandC = Mand (struct
  type elem = float
  type t = {re: elem; im:elem}
  let to_float x = x
  let of_float x = x
  let make ~re ~im = {re=re; im=im}
  let add a b = {re=a.re +. b.re; im=a.im +. b.im}
  let mul a b = {re=a.re *. b.re -. a.im *. b.im; 
		 im=a.re *. b.im +. a.im *. b.re}
  let norm2 z = z.re *. z.re +. z.im *. z.im
end)


Upd2: Справедливости ради, если сделать совсем кастомный аллокатор для класса, цифирь
начинает выглядеть так:

Time=2.2
Time=4.08
Time=49.42
Time=60.18

Но shared_ptr'ы все равно остаются ужасом и моральным террором.


       void * operator new (size_t size) {     
          if (free_list != NULL)                
            {                                    
              void * res = free_list;             
              free_list = *(void **)free_list;     
              return res;                           
            }                                        
            return ::operator new (size);             
       }                                               
       void operator delete (void * addr) {             
         if (addr == NULL) return;                       
         *(void **)addr = free_list;                      
          free_list = addr;                                
       }                                                    



(Читать комментарии) - (Добавить комментарий)


[info]kouzdra
2009-06-02 19:59 (ссылка)
Просто, поскольку куча по определению интенсивно используется - работа с ней сделана эффективно. За жабу не скажу, а O'Caml например, кроме прочего еще и кластеризует последовательные запросы к куче - на x86 выделение блока - 5 команд, причем с большой вероятностью будет размещено сразу несколько объектов.

Ну и всякие радости от gc - что не надо delete делать явно, что нету возни со счетчиками ссылок etc etc.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]vkni
2009-06-02 20:05 (ссылка)
Но тогда никто не мешает сделать кучу в C++ тоже эффективной? В смысле malloc/new/free/delete.

Просто стиль программирования такой, что тормоза в malloc/new/free/delete везде обходятся стороной.

Ну и конечно встроенный gc всегда будет быстрее всяких shared_ptr.

----------------

А есть возможность проверить фортран?

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]ppkk
2009-06-02 20:29 (ссылка)
Но тогда никто не мешает сделать кучу в C++ тоже эффективной?
Вот именно. Тормоза не с дизайном связаны, а с желанием создать тормоза. И стандартный <complex> не использован.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]vkni
2009-06-02 20:35 (ссылка)
Видите ли Пётр, во внутренних циклах выделение памяти из кучи в С++ обычно не делают. Поэтому в обсуждаемой ситуации есть немного от неуловимого Джо.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]ppkk
2009-06-02 21:25 (ссылка)
во внутренних циклах выделение памяти из кучи в С++ обычно не делают. Поэтому в обсуждаемой ситуации есть немного от неуловимого Джо
Не понял, почему такой тон, а выделение памяти из кучи во внутренних циклах делают. Например, если это не комплексные числа (для которых, на что никто не обращает внимание, дизайном, стандартом, чем угодно этого языка давно предусмотрены средства), а менее известного размера небольшие объекты (хотя бы строки существенно разной длины).
Я несколько раз сталкивался с неизбежной необходимостью (на другом языке) подстраивать динамическое выделение памяти программой под особенности управления памятью реализации языка (то есть ограничивал меня не дизайн, а именно реализация: в других версиях и результаты отличались). Для тех же строк во внутренних циклах, когда не было необходимости их редактировать, дошёл даже до выделения большого куска памяти и самостоятельного (ибо особо простой случай) управления ею.

Разъясню свою позицию, раз уж такие дела:
1. Быстродействие в этих ситуациях в бОльшей мере вопрос не дизайна языка, а его реализации (компилятора, интерпретатора и виртуальной машины, если требуется), причём возможно с большой зависимостью от ОС.
2. У Явы (или .Net) нет таких указателей, ограничивающих эффективное управление памятью. У них существенно другие кучи, чем в Цепепе.
3. Но дизайн языков с компиляторами не мешает написать своё (или взять готовое) управление памятью, пользоваться им без указателей адреса памяти и т.п., достигая быстродействия, идентичного явовскому.
4. В примерах не использованы нестандартные средства работы с комплексными числами, что уж совсем как-то вот слишком.
5. Быстродействие auto_ptr и shared_ptr [Boost, в отличие от <complex>, например, и auto_ptr пока не является частью стандартного Цепепе] действительно поставлено под сомнение, но применены они не по назначению. Разве используется RAII? Или множественное владение shared_ptr?
6. Если уж Boost, то надо было использовать scoped_ptr (он быстрее). Но и его пользовать смысла нет.
7. Ясно, что особо хитрый оптимизирующий компилятор Цепепе, хорошо рассчитанный на auto_ptr/shared_ptr теоретически мог бы справиться с кодом [info]kouzdra и обойтись вообще статическим выделением ячеек памяти, количество которых ясно из max_iter.

В общем, про особенности управления памятью можно хоть книжку с драконом читать, например, про сборку мусора и т.п. — тоже.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]vkni
2009-06-02 23:29 (ссылка)
> Не понял, почему такой тон, а выделение памяти из кучи во внутренних циклах делают.

Забыл :-) поставить.

(Ответить) (Уровень выше)


(Читать комментарии) -