LINUX.ORG.RU

QString и неявный шаринг данных

 ,


0

4

Задался совершенно детским вопросом. Как правильно быстро передавать QString в функцию, которая этот QString изменять не будет: по константной ссылке или по значению? Строго говоря, сам объект QString это прокси для неявно пошаренных данных, содержащий исключительно указатель на эти данные. То есть размер у прокси-объекта абсолютно аналогичен размеру ссылки. Недостатком ссылки является необходимость разыменовывать два указателя вместо одного (и не исключено что многократно). Недостатком объекта — отработка конструктора и деструктора с передёргиванием счётчика ссылок туда-сюда. При этом сам Qt поголовно использует для этих целей константные ссылки. Есть ещё какие-то аргументы? Или это исключительно читабельности ради?

★★★★★

по константной ссылке конечно же. зачем тебе дергать конструктор копирования?

Недостатком ссылки является необходимость разыменовывать два указателя вместо одного

не реккомендую тебе писать на С++, лучше фуру води или кирпичи ложи.

x0r ★★★★★
()
Ответ на: комментарий от yoghurt

Да я в общем и не парюсь. Просто внезапно задумался, что QString — умный указатель и что в процессе передачи указателя по константной ссылке есть какая-то тавтология.

KblCb ★★★★★
() автор топика
Ответ на: комментарий от KblCb

с таким подходом можно сразу вычеркнуть полиморфизм в С++ т.к. указатели/ссылки.

передача по значению оправдана разве что для скаляров. в случае объектов - это как минимум рантайм конструктора копирования, который для коллекций вообще имеет неопределенное значение.

в ряде случаев конструктор копирования приватный, и тогда кроме ссылок или указателей использовать нечего. или не дай бох КК дефолтный, а соотвествующий класс аггрегирует в себя указатель и вот уже заявочка на undefined behaviour после копирования.

при копировании QString у тебя скопируется весь массив с текстом. разыменование, по сравнению с этим, вообще ничто.

x0r ★★★★★
()
Последнее исправление: x0r (всего исправлений: 2)
Ответ на: комментарий от x0r

при копировании QString у тебя скопируется весь массив с текстом

В том-то и дело, что не скопируется. QString-и используют CoW

yoghurt ★★★★★
()
Ответ на: комментарий от x0r

с таким подходом можно сразу вычеркнуть полиморфизм в С++ т.к. указатели/ссылки.

Во-первых не весь, а только динамический :^). Во-вторых речь шла о частном случае и QString не полиморфен даже в теории (виртуального деструктора нет). Конструктор копирование QString делает ровно следующее: копирует указатель на данные (представленные другим объектом в куче) и автоинкрементирует счётчик ссылок. При попытке обратиться к не константному методу QString'а и значение счётчика больше единицы, данные будут скопированы и модифицироваться будет уже копия. По сути QString — умный указатель на данные строки.

Собственно речь шла о том, что при передаче QString'а (и почти всех прочих Qt-контейнеров по ссылке) мы будем вынуждены в дальнейшем вначале разыменовывать ссылку, а затем указатель на данные по этой ссылке. Вопрос состоит в том нафига передавать указатель по ссылке при том что цена копирования сравнительно мала, а цену лишнего косвенного перехода можно и завысит не странно ли вообще передавать указатель по ссылке? Ценность ссылок в общем случае я ни коим образом не отрицаю, но меня интересует именно частный.

KblCb ★★★★★
() автор топика
Ответ на: комментарий от KblCb

копирование QString делает ровно следующее

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

Вопрос состоит в том

меня лично это никак не смущает, я всегда смартпоинтеры передаю по ссылке не думая, сколько инструкций уйдет на копирование указателя, а сколько на двойное разыменование. демагогия на эту тему проект не завершит, а время отнимет.

x0r ★★★★★
()
Ответ на: комментарий от x0r

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

Спасибо, мой Капитан.

меня лично это никак не смущает, я всегда смартпоинтеры передаю по ссылке не думая, сколько инструкций уйдет на копирование указателя, а сколько на двойное разыменование. демагогия на эту тему проект не завершит, а время отнимет.

Мать моя женщина, это же ДЕМОГОГИИ тред. Я даже хотел какой-нибудь тег специальный добавить, но не нашёл. Меня интересует исключительно почему в сложившейся ситуации все делают так, а не иначе. Я вот пишу на Qt лет пять-шесть по меньшей мере, а задумался об этом впервые (или не впервые, а просто забыл уже, ну да не суть).

KblCb ★★★★★
() автор топика
Ответ на: комментарий от KblCb

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

ну так почитай Мейерса, у него этому глава присвящена:

Prefer pass-by-reference-to-const to pass-by-value

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

x0r ★★★★★
()
Ответ на: комментарий от x0r

ну так почитай Мейерса, у него этому глава присвящена

Я читал Мейерса. У него нет ничего про объекты исповедующие CoW. Я знаю, что копирующий конструктор может быть дорог или неопределён. Это не относится к Qt-контейнерам. Речь только про них.

KblCb ★★★★★
() автор топика

Передавай по константной ссылке.

Недостатком ссылки является необходимость разыменовывать два указателя вместо одного (и не исключено что многократно)

С чего ты это взял? Желательно с пруфами, лучше всего на ассемблерный код.

Chaser_Andrey ★★★★★
()

Вы до сих пор верите в то, что ваши ссылки/значения имеют смысл?

Я тебе скажу, как быстро что-то передавать - inline f(QString str); Во всех 99.(9)% случаев.

Портянка без каллов быстрее портянки с каллами уже десят лет, после того, как на х86 появился вменяемый аппаратный префеч и гцц об этом знает, но иногда тупит - поэтому приучайся на всё ставить инлайн.

Можешь даже забенчить, если тебе не лень - и пацанам расскажешь.

g++ -S тебе в помощь, ибо я ведать не ведаю что из себя представляет кустринг. Если этот щит конпелятор сумеет превратить в указатель, то как ты его будешь передавать реально пофиг.

Если он валяется у тебя где-то в стеке, разницы тоже особо нет.

procoder99
()
Ответ на: комментарий от KblCb

Возьми QtestLib и сделай там QBENCHMARK с копированием милльона строк или с передачей их по ссылке и сравни время.

Gorthauer ★★★★★
()
Ответ на: комментарий от Gorthauer

и сравни время.

ну и пацанам покажи, конечно.

dib2 ★★★★★
()
Ответ на: комментарий от Chaser_Andrey

он имеет ввиду, что ссылка на QString - это указатель по своей физической сути, а QString - это обертка над указателем на QStringPrivate, который хранит собственно данные. При любом доступе к данным (по крайней мере в первый раз), надо будет ходить через два поинтера.

dib2 ★★★★★
()

КО сообщаэ:

самому стало интересно, быстренько набыдлокодил тест, и вот что получилось:


  • 10000000 вызовов функций с передачей по значению, и по константной ссылке.
  • Целевая архитектура: x86_64-unknown-linux-gnu
  • Параметры конфигурации: /build/gcc/src/gcc-4.8.1/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --enable-gnu-unique-object --enable-linker-build-id --enable-cloog-backend=isl --disable-cloog-version-check --enable-lto --enable-gold --enable-ld=default --enable-plugin --with-plugin-ld=ld.gold --with-linker-hash-style=gnu --disable-install-libiberty --disable-multilib --disable-libssp --disable-werror --enable-checking=release
  • Модель многопоточности: posix
  • gcc версия 4.8.1 (GCC)
  • сборка - -O2, march=native, mtune=native (не влияет на результаты, как и -O3)
  • Intel Core i7, 2GHz, все CPU scaling governors переведены в performance.
  • Linux klaptop 3.9.3-pf #1 SMP PREEMPT Thu May 30 22:20:48 EEST 2013 x86_64 GNU/Linux



Если переданный QString в функции используется 1 раз, то:

$ ./refvsobject 
Obj: 691 ms
Ref: 556 ms
Ref is faster than obj 19.5369 %
$ ./refvsobject
Obj: 824 ms
Ref: 559 ms
Ref is faster than obj 32.1602 %
$ ./refvsobject
Obj: 681 ms
Ref: 565 ms
Ref is faster than obj 17.0338 %
$ ./refvsobject
Obj: 674 ms
Ref: 558 ms
Ref is faster than obj 17.2107 %
$ ./refvsobject
Obj: 788 ms
Ref: 561 ms
Ref is faster than obj 28.8071 %


Если переданный QString используется 3 раза в функции (вызовы mid, left, right):

$ ./refvsobject 
Obj: 1886 ms
Ref: 1869 ms
Ref is faster than obj 0.901379 %
$ ./refvsobject
Obj: 1890 ms
Ref: 1832 ms
Ref is faster than obj 3.06878 %
$ ./refvsobject
Obj: 1983 ms
Ref: 1844 ms
Ref is faster than obj 7.00958 %
$ ./refvsobject
Obj: 1887 ms
Ref: 1850 ms
Ref is faster than obj 1.96078 %
$ ./refvsobject
Obj: 1910 ms
Ref: 1850 ms
Ref is faster than obj 3.14136 %


Вот так вот. Собственно, сокращение разрыва и говорит о больших первоначальных затратах на копирование объекта при вызове функции.

ЗЫ. это при закрытых приложениях. До тестов было запущено куча говна, включая VirtualBox - результаты бегали и прыгали, так что и передача объекта была почти всегда быстрее ссылки о_О.

dib2 ★★★★★
()

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

m0rph ★★★★★
()
Последнее исправление: m0rph (всего исправлений: 1)
Ответ на: комментарий от trex6

Это уже не принципиально, ибо конпеляторы умеют лто. Даже если твой конпелятор не умеет лто - пиши инлайны в хедерах.

Жалкая придирка.

procoder99
()

Как правильно быстро передавать QString в функцию, которая этот QString изменять не будет: по константной ссылке или по значению?

Да ты упорот. Конечно по константной ссылке.

nanoolinux ★★★★
()
Ответ на: комментарий от m0rph

Увеличение счетчика ссылок - атомарная операция, которая теоретически может влиять на состояние процессорного конвейера.

И не только. Это 1. блокирование других процессоров, 2. занятние интерконнекта между процессорами, 3. сбросы изменяемой линии кеша во всех процессорах

Кол-во приключений в общем составляет до 7 сотен тактов процессора в худшем случае.

Pavval ★★★★★
()
Ответ на: комментарий от sjinks

А какая разница. Твой код один фиг не влезет в l1i, а что там дальше имеет одинаковый перфоманс.

Лет 10назад возможно, когда х86 не умел префечить - сейчас для х86 это не актуально и то, если твоя функция влезает в l1i. Но я не думаю, что то, что влезает в l1i как-то связанно с С++ и кути.

Он запрефечит l1i быстрее, чем исполнится код, который он скушал до этого.

procoder99
()
Ответ на: комментарий от sjinks

Вот тебе бенч, слепил из того, что я пилил в последнее рвемя.

http://pastebin.com/NYqm37Vy

-fno-inline, юз 2-х функций - погляди.

perf stat -e L1-icache-loads,L1-icache-load-misses,L1-icache-prefetches,L1-icache-prefetch-misses ./a.out 

        5481119165 L1-icache-loads                                             
            119637 L1-icache-load-misses     #    0.00% of all L1-icache hits  
       1.742712386 seconds time elapsed
        5616803423 L1-icache-loads                                             
            112204 L1-icache-load-misses     #    0.00% of all L1-icache hits  
       1.787479163 seconds time elapsed

Портянка с инлайном и без инлайна. Эта байда не влезет в l1i, поменяй её. Мирез от l1i миссов даже не стоит вызовов функции.

А уж фуллунтролл просто сливает всё.

Причем заметь - эти функции просто в хламину напрягую твой х86, а вот от С++ щитфункций, да и вообще от рядовой функции твой х86 будет тупо стоять.

procoder99
()
Ответ на: комментарий от procoder99

add_and_mul_unroll() у меня полностью помещается в кэше инструкций, надо будет подкорректировать N и доразвернуть код — но это завтра.

Так как оно помещается в кеш, очевидно, что в текущем виде add_and_mul_unroll() будет быстрее.

По идее результат должен различаться, если развернуть цикл в main() — оптимизатор сам это сделать не может из-за volatile переменных (если собрать код с -f-unroll-loops -funroll-all-loops, изменится только паттерн доступа к памяти, причём не лучшим образом, а цикл в main() не развернётся).

sjinks ★★★
()
Ответ на: комментарий от sjinks

Так как оно помещается в кеш, очевидно, что в текущем виде add_and_mul_unroll() будет быстрее.

Удиви меня тем, что по твоему не помещается.

По идее результат должен различаться, если развернуть цикл в main() — оптимизатор сам это сделать не может из-за volatile переменных (если собрать код с -f-unroll-loops -funroll-all-loops, изменится только паттерн доступа к памяти, причём не лучшим образом, а цикл в main() не развернётся).

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

Давай я жажду примеров.

procoder99
()
Ответ на: комментарий от sjinks

Так, я немного развернул цикл в main(): http://x3me.me/g

gcc -O2 -masm=intel -S выдал такое: http://x3me.me/h (add_and_mul_unroll он разворачивать отказался).

Добавил к функции __attribute__((always_inline)), функция развернулась.

Время работы (unroll — незаинлайненная функция, развернуто 10 итераций цикла; unroll-10, unroll-20, unroll-40 — заинлайненная функция, развёрнуто 10, 20 и 40 итераций цикла соответственно):

$ time ./unroll; time ./unroll-10; time ./unroll-20; time ./unroll-40

real    0m3.269s
user    0m3.260s
sys     0m0.000s

real    0m3.493s
user    0m3.484s
sys     0m0.000s

real    0m5.111s
user    0m5.096s
sys     0m0.000s

real    0m5.179s
user    0m5.164s
sys     0m0.000s
sjinks ★★★
()
Ответ на: комментарий от procoder99

Давай я жажду примеров.

См. выше

Он это сделать не сможет не потому, что там volatile, а потамучто он глупый.

Легко проверяется экспериментально: убери volatile, и он всё развернёт. А DCE еще и мусор почистит.

sjinks ★★★
()
Ответ на: комментарий от procoder99

Смотри за руками:

$ for i in unroll unroll-10 unroll-20 unroll-40; do echo $i; perf stat -e L1-icache-loads,L1-icache-load-misses,L1-icache-prefetches,L1-icache-prefetch-misses ./$i; done
unroll

 Performance counter stats for './unroll':

     2 034 995 692 L1-icache-loads                                             
           160 623 L1-icache-load-misses     #    0,01% of all L1-icache hits  
                56 L1-icache-prefetches                                        
   <not supported> L1-icache-prefetch-misses

       3,737928023 seconds time elapsed

unroll-10

 Performance counter stats for './unroll-10':

     2 010 939 929 L1-icache-loads                                             
           943 250 L1-icache-load-misses     #    0,05% of all L1-icache hits  
                54 L1-icache-prefetches                                        
   <not supported> L1-icache-prefetch-misses

       3,505826840 seconds time elapsed

unroll-20

 Performance counter stats for './unroll-20':

     3 348 544 140 L1-icache-loads                                             
     1 005 071 340 L1-icache-load-misses     #   30,02% of all L1-icache hits  
                55 L1-icache-prefetches                                        
   <not supported> L1-icache-prefetch-misses

       5,119926313 seconds time elapsed

unroll-40

 Performance counter stats for './unroll-40':

     3 294 566 801 L1-icache-loads                                             
     1 004 573 451 L1-icache-load-misses     #   30,49% of all L1-icache hits  
               108 L1-icache-prefetches                                        
   <not supported> L1-icache-prefetch-misses

       5,222613108 seconds time elapsed

Смотри, как растут L1-icache-load-misses

sjinks ★★★
()
Ответ на: комментарий от sjinks

Ты написал тотальшейщий щит.

        mov     rax, QWORD PTR [rsp]
        sub     rax, 1
        mov     QWORD PTR [rsp], rax
        mov     QWORD PTR [rsp+8], OFFSET FLAT:m_acc
        mov     QWORD PTR [rsp+16], OFFSET FLAT:m
        mov     QWORD PTR [rsp+24], OFFSET FLAT:m+8192
        mov     rdx, QWORD PTR [rsp+24]
        mov     rsi, QWORD PTR [rsp+16]
        mov     rdi, QWORD PTR [rsp+8]
        call    add_and_mul_unroll.constprop.0



Что это за щит? Не показывай мне больше этот ущербанский синтаксис.


Ты бенчишь тормазнутость этого фуфла, а не то, о чём ты подумал. Либо разворачивай вменяемо( это КАК Я сделал), либо не страдай фигнёй.
procoder99
()
Ответ на: комментарий от procoder99

Ты написал тотальшейщий щит.

Протри глаза, это выхлоп gcc -S

Не показывай мне больше этот ущербанский синтаксис.

Ну собери сам с gcc -S -masm=att, руки, надеюсь, не отвалятся?

Ты бенчишь тормазнутость этого фуфла

Ха, ты сам volatile туда влепил. Расплачивайся, что называется. В твоём оригинальном решении цикл в main() не разворачивался; эти безумные load/store выполнялись точно так же, но в цикле. Так что уж определись, что ты сам мерял. И вообще, будь мужиком и посмотри в cachegrind, где именно у тебя идут cache misses.

Я тебе цикл развернул:

      acc = m_acc; add = m; add2 = (m + N * 2);
        add_and_mul_unroll(acc, add, add2, mul);
      --i;

      acc = m_acc; add = m; add2 = (m + N * 2);
        add_and_mul_unroll(acc, add, add2, mul);
      --i;

      …

и заставил gcc развернуть твою типа вменяемую функцию.

Теперь кеша L1i не хватает, чтобы вместить все копии add_and_mul_unroll(), в результате чего производительность сильно падает. Весь фокус в __attribute__((always_inline)).

sjinks ★★★
()
Ответ на: комментарий от sjinks

Воперх - ты делаешь бесполезный щит. Ты уже анролишь мой итак в хлам занроленный вариант, хотя я даже этого не заметил. Я даже не знаю как до такого можно дойти.

procoder99
()
Ответ на: комментарий от procoder99

Держи развёрнутый код, а то, боюсь, что сам не развернёшь: http://x3me.me/i

unroll-40 — это версия с __attribute__((always_inline)), unroll-40a — без.

$ for i in unroll-40 unroll-40a; do echo $i; perf stat -e L1-icache-loads,L1-icache-load-misses,L1-icache-prefetches ./$i; done
unroll-40

 Performance counter stats for './unroll-40':

     3 301 751 350 L1-icache-loads                                             
     1 001 950 243 L1-icache-load-misses     #   30,35% of all L1-icache hits  
                84 L1-icache-prefetches                                        

       5,070544283 seconds time elapsed

unroll-40a

 Performance counter stats for './unroll-40a':

     2 030 342 721 L1-icache-loads                                             
           158 750 L1-icache-load-misses     #    0,01% of all L1-icache hits  
                52 L1-icache-prefetches                                        

       3,298386792 seconds time elapsed

Разница в коде — это то, что ты назвал «Портянка без каллов» против «портянки с каллами». А иой пример тебе явно показывает, что код с call может быть даже очень быстрее кода без call — из-за того, что L1i не бесконечный.

sjinks ★★★
()
Ответ на: комментарий от procoder99

Ты уже анролишь мой итак в хлам занроленный вариант, хотя я даже этого не заметил.

Смысл в том, чтобы твой хлам не помещался в L1i. Самый простой способ — развернуть для этого цикл в main(). Можно попробовать увеличивать N и разворачивать твой хлам с учётом размера N — но мне банально лень. При значении N, когда твой unroll перестанет помещаться в L1i, ты получишь те же самые проблемы, что и при разворачивании цикла в main.

sjinks ★★★
()
Ответ на: комментарий от sjinks

Ха, ты сам volatile туда влепил. Расплачивайся, что называется. В твоём оригинальном решении цикл в main() не разворачивался; эти безумные load/store выполнялись точно так же, но в цикле. Так что уж определись, что ты сам мерял. И вообще, будь мужиком и посмотри в cachegrind, где именно у тебя идут cache misses.

Я впихнул и я знаю почему. Я знаю где они идут.

и заставил gcc развернуть твою типа вменяемую функцию.

Дак он каллит её - ты её не развернул.

Теперь кеша L1i не хватает, чтобы вместить все копии add_and_mul_unroll(), в результате чего производительность сильно падает. Весь фокус в __attribute__((always_inline)).

Ты несёшь какую-то херню - ты не заинлайнил функцию, ты её заанролил, причем бессмысленно.

Я разве призывал бессмысленно анролить циклы на милионны инструкций подрят?

Нет, я показал тебе пример зааонролив цикл в 100раз. Больше этой портянки в реальном мире у тебя никогда не будет кода.

procoder99
()
Ответ на: комментарий от sjinks

Чтобы это начало тормазить - тебе надо увеличить N раза в 3-4, чего в реальном мире не бывает. Такого просто не бывает.

Я тебе показал, что на реальных задачах l1i бесконечный, и об этом можно не беспокоится.

Только вот значение N у меня никогда не будет - ибо я юзаю не анрольные функции, которые мне надо 500штук, чтобы у меня тормазило, хотя у меня даже 10штук нет.

Т.е. я могу спокойно кажду из них анролить раз в 50.

procoder99
()
Ответ на: комментарий от procoder99

Дак он каллит её - ты её не развернул.

Да глаза же ты раскрой наконец! С просто inline он её и не будет пытаться разворачивать, потому как в этом случае call будеьт быстрее.

Ты несёшь какую-то херню - ты не заинлайнил функцию, ты её заанролил, причем бессмысленно.

Херню сейчас несёшь ты. Unroll применимо только к циклам. А функция именно встраивается. Только ты не понял, зачем. Поэтому поясняю на пальцах.

Допустим, что у тебя есть функции f1() и f2(), причём такие, что суммарный размер f1() и f2() превышает размер кеша L1i. Допустим, что у тебя есть цикл, который выполняется большое количество раз:

while (something) {
     f1();
     f2();
}

Смысл в том, что вызовы в этом случае будут более производительны, чем встраивание — именно из-за L1i cache misses.

Это именно тот случай, когда твоё утверждение, что «Портянка без каллов быстрее портянки с каллами уже десят лет» неверно.

Comprenez-vous?

sjinks ★★★
()
Ответ на: комментарий от sjinks

Вобщем спор из разряда «аесли бы да кабы». А если из твоего кода сделаю щит, а если ты как идиот заанролишь в ПЯТЬ тысяч раз твою функцию. А если ты в одну функцию заинлайнишь 1к средних функций.

Кстати, а попробуй выпилить вообще цикл.

procoder99
()
Ответ на: комментарий от sjinks

Допустим, что у тебя есть функции f1() и f2(), причём такие, что суммарный размер f1() и f2() превышает размер кеша L1i. Допустим, что у тебя есть цикл, который выполняется большое количество раз:

Тогда у тебя профита от call не будет, ибо:

while (something) {
     f1();//предположим только она влазиет в l1i.
     f2();//тут наш l1i сбросится, в него запишется f2(), f1() так уже не будет и в следующей итерации она будет заного туга загружена.
}

Если ты функции заинлайнишь - поведение будет такое же, даже будет быстрее.

Я думаю ты поймёшь почему.

procoder99
()
Ответ на: комментарий от sjinks

Ты пытаешься искать оправдание своим словам в тех дебрях, в которых тебя калл не спасёт.

Калл тебя спасёт, если у тебя будет одна фукция, которая занимает 95% l1i. И то, эта функция тупо инлайнится и работает ещё быстрее без call.

while()
 call f();
заменяется на:

while()
  тело функции f;

Второй вариант будет быстрее во всех случаях, кроме тех, когда ты бездумно будешь пытаться анрольнуть одинаковую функцию.

procoder99
()

В конечном итоге мы дошли до того, что inline всегда быстрее call'ов.

Если уж l1i кончится - он кончится и на call'ах, а оверхеда на вызовы функций не будет + оптимизации от конпелятора.

В плюсах профита будет ещё больше, ибо тут ещё больше оверхеда на калл функции.

Кстати, в gnu Си инлайн инлайнится всегда, ибо inline значит, что функции не будет в объектнике и она только инлайнится.

procoder99
()
Ответ на: комментарий от KblCb

Все просто: если ты передаешь по значению, получаешь оверхед на инкремент атомарного счетчика ссылок при создании копии и на декремент при уничтожении, если передаешь по константной ссылке, то не получаешь.

annulen ★★★★★
()
Ответ на: комментарий от procoder99

В конечном итоге мы дошли до того, что inline всегда быстрее call'ов.

Да ты чё! Пантентуй, а то уведут идею.

schizoid ★★★
()
Ответ на: комментарий от procoder99

Я думаю ты поймёшь почему.

Я знаю, что ты имеешь в виду, но так случается далеко не всегда. Вряд ли весь код функции сразу будет в L1i. Такое возможно только в однозадачной системе. На практике же случаются переключения контекста и за те 5 секунд выполнения кода может выполниться не одна функция. Если бы кеш полностью очищался при каждом переключении контекста, были бы огромные тормоза.

В случае без встраивания функции шансы на то, что её код будет находиться в одной из линий кеша выше, чем в случае без встраивания (что будет заметно, если функция нелинейная). Именно поэтому gcc далеко не всегда разворачивает функции, который встречаются даже один раз.

sjinks ★★★
()
Ответ на: комментарий от procoder99

Кстати, в gnu Си инлайн инлайнится всегда

Учи матчасть. inline — подсказка компилятору, а не руководство к действию. Ассемблерные листинги выше это подтверждают, к слову.

inline значит, что функции не будет в объектнике

А вот фиг. Так, как определил её ты, она в объектнике будет — ибо man visibility. Проверяется выхлопом gcc -S. Если ты хочешь, чтобы её не было в объектнике — делай её статической, либо задавай дефолтную видимость hidden.

sjinks ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.