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

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]ppkk
2009-06-02 19:34 (ссылка)
Ну и стандартный модуль для работы с комплексными числами, полагаю, разумно было бы использовать, следуя дизайну языка (ибо STL входит в дизайн, а <complex> — в STL, хоть ты это и не любишь).

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


[info]ketmar
2009-06-02 19:39 (ссылка)
от stl надо бежать подальше с дикими воплями ваще, ящитаю. обосновать не могу.

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


[info]kouzdra
2009-06-02 19:44 (ссылка)
Во-во - я даже могу - С++ все-таки без кастомизации приводит к жопе. Свежий пример - чего они там в shared_ptr такого наворотили, что он в разы скорость сажает - счетчики ссылок - тормозной метод, но не настолько же.

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

В Java, что характерно, эта оптимизация бессмысленна: она на скорость не влияет.

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


[info]ketmar
2009-06-02 19:51 (ссылка)
>В Java, что характерно, эта оптимизация бессмысленна: она на скорость не влияет.
ORLY?! O_O в жабе настолько всё херово?

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


[info]kouzdra
2009-06-02 19:52 (ссылка)
Напротив - все настолько хорошо :)

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


[info]ketmar
2009-06-02 19:53 (ссылка)
э… а каким фигом может быть «хорошо», если как не извращайся с выделением памяти, скорость всё равно не увеличивается? мне кажется, это не «у жабы классно память сделана», а «жаба настолько угрёбищна, что даже трюки с облегчением работы memory manager ей не помогают». %-)

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


[info]kouzdra
2009-06-02 19:56 (ссылка)
если как не извращайся с выделением памяти, скорость всё равно не увеличивается?

А ей некуда увеличиваться. Затраты с тем, что "все в куче" там (как и в ML) не на выделение памяти, а на лишние косвенности и всякие vmt внутри объектов. А оптимизить саму операцию new там действително практически бессмысленно.

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


[info]ketmar
2009-06-02 19:52 (ссылка)
я-то не могу просто потому, что не подружился с stl с самого начала. соответственно, маловато «инсайда» и тестов, чтобы обоснованно ругаться.

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


[info]q
2009-06-02 23:17 (ссылка)
чего они там в shared_ptr такого наворотили

Ясно чего.

shared_ptr - он прозрачен для работы с нитками, то есть чтение-копирование - атомарны. Следовательно, работа со счётчиком ссылок по дефолту обвязывается мутексами.

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

Кстати, в boost (откуда он и взят чуть менее, чем целиком) имеется несколько дефайнов времени компиляции для ускорения работы; среди них - дефайн "ниток не будет, мутексов не надо".

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


[info]kouzdra
2009-06-02 23:58 (ссылка)
shared_ptr - он прозрачен для работы с нитками, то есть чтение-копирование - атомарны. Следовательно, работа со счётчиком ссылок по дефолту обвязывается мутексами

Кстати - к вопросу о "функциональных языках" - почти все любители попинать "высокую функциональную идею" не допирают например до простой вещи - чисто функциональные структуры данных не нужнаются в mutex'aх. Что в мультитредных приложениях обычно с лихвой перекрывает издержки на их организацию (которые выражаются не слишком большим константным множителем). Не говоря уж о радикальном упрощении логики.

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


[info]max630.livejournal.com
2009-06-03 08:03 (ссылка)
ну зачем так передёргивать, мы же говорим о потрохах умных указателей, вы таки хотите сказать что сборка мусора для мультитредных программ обходится без мутексов?

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


[info]kouzdra
2009-06-03 13:33 (ссылка)
Сборщик мусора - операция сравнительно редка и выполняющаяся сравнительно крупными блоками. А тут, если например надо по дереву пробежать, надо либо лочить все дерево (что может привести к проседанию UI, либо на каждый переход по ссылке, что - тормоза, либо извращаться чтобы и не слишком часто, и не слишком редко.

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


[info]q
2009-06-03 08:38 (ссылка)
Структуры данных, использующие shared_ptr, тоже не нуждаются в мутексах. Мутексы - они внутре.

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


[info]kouzdra
2009-06-03 13:31 (ссылка)
Так это и значит, что нуждаются. Но на самом деле - нуждаются и вовне - потому что целостность структуры в момент работы с ней надо обеспечивать.

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


[info]q
2009-06-03 15:23 (ссылка)
Ну так если виртуальная машина функционального языка будет использовать внутри себя ленивое копирование в каком-либо виде, то и там нужны будут мутексы, чудес не бывает.

А если копирование честное, то никакой shared_ptr не будет использоваться, и мутексы не будут нужны и в C++.

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


[info]kouzdra
2009-06-03 15:30 (ссылка)
Ну так если виртуальная машина функционального языка

Там нет обычно никаких виртуальных машин. Это не Java же. И при чем тут "копирование"? Копировать ссылку на неизменяемую структуру можно сколько угодно без всяких мутексов.

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

Если дерево не мутирует - не нужны и мутексы. К функциональым языка это не имеет отношения (хотя для них это естественный стиль) - immutable структуры можно и на C писать - зато относится к pure functional data structures. И к сборке мусора, потому что без сборки мусора их программировать крайне трудно, да и использовение ref.counts убивает сам смысл затеи - ибо структура оказывается мутирующей из-за этих самых счетчиков.

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


[info]sasha_gil
2009-06-03 18:44 (ссылка)
>>>И к сборке мусора, потому что без сборки мусора их программировать крайне трудно, да и использовение ref.counts убивает сам смысл затеи - ибо структура оказывается мутирующей из-за этих самых счетчиков.

Тут я бы поспорил: а) использование везде смартпойнтеров, скрывающих рефкаунт-возню, действительно здорово упрощает программирование конкретной прикладной задачи (сами смартпойнтеры аккуратно запрограммировать -- это труд, но он делается один раз и можно взять уже запрограммированные, как ты в этом примере) и б) плохие рэйс кондишены возникают из-за логической мутируемости структуры. "Физическая" мутируемость из-за рефкаунтов логическую мутируемость не ломает, проблемы не создаёт. Кстати не понял про необходимость мьютексов -- под виндой я пользуюсь вроде бы более лёгкими InterlockedIncrement / Decrement, такого в Линуксе что ли нет?

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


[info]q
2009-06-03 21:40 (ссылка)
Под линуксом есть много чего, в том числе и политика сборки boost совсем без мутексов, но полностью корректными (особенно на многопроцессорных системах) могут быть только настоящие, в случае линукса - libpthread. Это довольно тонкий вопрос, в котором легко сделать ошибку.

Про применимость InterlockedIncrement / Decrement ничего не могу сказать, не знаю.

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


[info]max630.livejournal.com
2009-06-03 23:50 (ссылка)
выбор там такой:

static const _Lock_policy __default_lock_policy =
#ifdef __GTHREADS
#if (defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) \
&& defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4))
_S_atomic;
#else
_S_mutex;
#endif
#else
_S_single;
#endif

и вот сейчас оно использует _S_atomic, то есть это не мутекс, как я понимаю.

я переткнул на _S_single и оно особо не поменялось.

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


[info]ppkk
2009-06-05 22:01 (ссылка)
Если я правильно понял, то для реализации потенциальных преимуществ "функциональных языков" по скорости выполнения давно придуманы SSA/SSU оптимизации.

Понятное дело, что всё равно придётся программировать.

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

И ещё о сборке мусора: если это законченная задача (выдаёт числовой ответ), а не часть сложной программы, то сборка мусора очень тупо реализуется средствами ОС. То есть: истекающая памятью программка собирается в исполняемый файл, который выдаёт ответ и завершает работу. Всё: система сама приберётся. А на возражения можно ответить тем, что тест должен быть адекватным: например, чтобы без заботы о памяти программа просто не могла выполнить работу.

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


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