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


35

TD; DR:

Була певна плутанина щодо того, що я просив, тож ось ідея, яка рухає за питанням:

Я завжди мав намір питання бути таким, яким воно є. Я, можливо, не сформулював це добре спочатку. Але намір завжди був " модульний, відокремлений, вільний зв'язаний, роз'єднаний, реконструйований код " помітно повільніше за своєю природою, ніж " монолітний одноодиничний, зробіть все в одному місці, один файл, щільно пов'язаний " код. Решта - це лише деталі та різноманітні прояви цього, на які я стикався тоді чи зараз, або згодом. У певному масштабі це точно повільніше. Як і диск, що не відшаровується, вам потрібно збирати шматки звідусіль. Це повільніше. Напевно. Але я повинен дбати?

І питання не в тому ...

не про мікрооптимізацію, передчасну оптимізацію тощо. Це не про "оптимізацію тієї чи іншої частини до смерті".

Що це тоді?

Йдеться про загальну методологію та прийоми та способи мислення про написання коду, що з’явився з часом:

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

Йдеться про спосіб і стиль коду, який зараз - протягом цього десятиліття - бачать та обстоюють у більшості рамок, виступають на конвенціях, передаються через громаду. Це зміна мислення від «монолітних блоків» до «мікросервісів». І з цим приходить ціна в плані продуктивності на рівні машини та накладних витрат, а також на деяких накладних на рівні програміста.

Оригінальне запитання наступне:

У галузі комп'ютерних наук я помітив помітне зрушення в мисленні, коли мова йде про програмування. Я досить часто зустрічаю поради, які виглядають так:

  • писати менший функціональний код (більш тестований та підтримуваний таким чином)
  • рефактор існуючого коду на все менші і менші шматки коду, поки більшість ваших методів / функцій не мають лише кілька рядків, і зрозуміло, яке їх призначення (що створює більше функцій порівняно з більшим монолітним блоком)
  • писати функції, які виконують лише одне - роз'єднання проблем тощо (що зазвичай створює більше функцій та більше кадрів на стеку)
  • створити більше файлів (один клас на файл, більше класів для розкладання, для цілей шару, таких як MVC, архітектура домену, шаблони дизайну, OO тощо), що створює більше викликів файлової системи)

Це зміна в порівнянні зі «старими» або «застарілими» або «спагетті» методами кодування, де у вас є методи, що охоплюють 2500 рядків, а великі класи та об'єкти бога роблять все.

Моє запитання таке:

коли виклик зводиться до машинного коду, до 1s і 0s, до інструкцій по збірці, до платівок жорсткого диска, чи повинен я взагалі стурбований тим, що мій ідеально відокремлений клас OO з різноманітними відреставрованими дрібними та крихітними функціями та методами також генерує набагато зайвих накладних витрат?

Деталі

Хоча я не дуже добре знайомий з тим, як в кінцевому підсумку обробляється код OO та його виклики методів, і як виклики БД та виклики компілятора переводяться на переміщуючу ручку виконавчого механізму на платівці жорсткого диска, у мене є певна ідея. Я припускаю, що кожен виклик додаткової функції, виклик об'єкта або виклик "#include" (на деяких мовах) генерують додатковий набір інструкцій, тим самим збільшуючи об'єм коду та додаючи різні накладні витрати "проводка коду", не додаючи фактичного "корисного" коду . Я також уявляю, що хороші оптимізації можна зробити ASM ще до того, як він фактично запуститься на апаратному забезпеченні, але оптимізація може зробити також дуже багато.

Отже, моє запитання - скільки накладних витрат (у просторі та швидкості) фактично вводить добре відокремлений код (код, розбитий на сотні файлів, класів та шаблонів дизайну тощо), порівняно із тим, що є "один великий метод, який містить все в одному монолітному файлі ", через цю накладну?

ОНОВЛЕННЯ для наочності:

Я припускаю, що прийняття одного і того ж коду та розбиття його, рефакторинг, декомпозиція його на все більше функцій та об'єктів, методів і класів призведе до того, що все більш і більше параметрів передаватимуться між меншими фрагментами коду. Тому що, безумовно, код рефакторингу повинен тримати потік потоку, і це вимагає передачі параметрів. Більше методів або більше класів або більше моделей проектування заводських методів призводить до того, що більше витрат на передачу різних бітів інформації більше, ніж це стосується одного монолітного класу або методу.

Десь було сказано (цитуйте TBD), що до 70% всього коду складається з інструкції MOV ASM - завантаження регістрів процесора з належними змінними, а не власне обчислення. У моєму випадку ви завантажуєте час процесора за допомогою інструкцій PUSH / POP, щоб забезпечити зв'язок та параметри, що проходять між різними фрагментами коду. Чим менше ви будете робити свої шматочки коду, тим більше потрібно накладних "зв'язків". Я стурбований тим, що ця зв'язок додає розростання та уповільнення програмного забезпечення, і мені цікаво, чи варто мені це турбувати, і скільки, якщо взагалі є, тому що нинішні та майбутні покоління програмістів, які будують програмне забезпечення для наступного століття , доведеться жити і споживати програмне забезпечення, побудоване за цими методами.

ОНОВЛЕННЯ: Кілька файлів

Я зараз пишу новий код, який повільно замінює старий код. Зокрема, я зазначив, що один із старих класів був файлом рядків ~ 3000 (як згадувалося раніше). Тепер він стає набором з 15-20 файлів, розташованих у різних каталогах, включаючи тестові файли та не включаючи рамки PHP, які я використовую, щоб зв’язати деякі речі разом. Також надходить більше файлів. Що стосується вводу / виводу диска, завантаження декількох файлів відбувається повільніше, ніж завантаження одного великого файлу. Звичайно, не всі файли завантажуються, вони завантажуються у міру необхідності, а також є кешування дисків та кешування пам’яті, і все ж я вважаю, що це loading multiple filesпотребує більшої обробки, ніж loading a single fileу пам’яті. Я додаю це до своєї стурбованості.

ОНОВЛЕННЯ: Залежність Введіть все

Повернувшись до цього через деякий час .. Я думаю, що моє запитання було зрозуміло неправильно. А може, я вирішив неправильно зрозуміти деякі відповіді. Я не говорю про мікрооптимізацію, як окремі відповіді виділили (принаймні, я думаю, називати те, що я говорю про мікрооптимізацію - це неправильно), а про рух "коду Refactor для послаблення жорсткої муфти", в цілому , на кожному рівні коду. Я приїхав із Zend Con зовсім недавно, де цей стиль коду був одним із основних моментів і центральних пунктів конвенції. Розв'яжіть логіку з виду, перегляньте з моделі, модель з бази даних, і, якщо можете, від'єднайте дані з бази даних. Dependency - Введіть все, що іноді означає просто додавання коду проводки (функції, класи, панель котла), що нічого не робить, але служить точкою шва / гачка, в більшості випадків легко збільшується вдвічі розмір коду.

ОНОВЛЕННЯ 2: Чи "розділення коду на більше файлів" суттєво впливає на продуктивність (на всіх рівнях обчислень)

Як філософія compartmentalize your code into multiple filesвпливає на обчислення на сьогодні (продуктивність, використання диска, управління пам’яттю, завдання обробки процесора)?

Я говорю про

Перед ...

У гіпотетичному, але цілком реальному не настільки далекому минулому, ви можете легко написати один моноблок файлу, який має модель і вигляд та контролер спагетті або не-спагетті, кодований, але він запускає все, як тільки він уже завантажений. Здійснюючи деякі орієнтири в минулому за допомогою коду С, я виявив, що набагато швидше завантажувати в пам'ять один 900 Мбіт-файл і обробляти його великими шматками, ніж це завантажувати купу менших файлів і обробляти їх у меншій кількості спокійних страв шматки, що зрештою виконують ту саму роботу.

.. А зараз*

Сьогодні я виявляю код, який показує головну книгу, яка має такі функції, як .. якщо елемент є "замовленням", покажіть HTML-блок замовлення. Якщо позицію можна скопіювати, надрукуйте блок HTML, який відображає піктограму та параметри HTML, що дозволяють зробити копію. Якщо елемент можна переміщувати вгору або вниз, відобразіть відповідні стрілки HTML. І т. Д. Я можу через Zend Framework створитиpartial()call, що по суті означає "викликати функцію, яка приймає ваші параметри та вставляє їх в окремий файл HTML, який він також викликає". Залежно від того, наскільки детально я хочу отримати, я можу створити окремі функції HTML для найдрібніших частин книги. Один для стрілок вгору, стрілка вниз, друга для "чи можу я скопіювати цей елемент" тощо. Легке створення декількох файлів лише для відображення невеликої частини веб-сторінки. Беручи мій код і позакулісний код Zend Framework, система / стек, ймовірно, викликає близько 20-30 різних файлів.

Що?

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

Наприклад, завантаження більшої кількості файлів означає розміщення їх у різних місцях файлової системи та в різних місцях фізичного жорсткого диска, що означає більше часу на пошук та читання жорсткого диска.

Для процесора це, ймовірно, означає більше контекстної комутації та завантаження різних регістрів.

У цьому підблоці (оновлення №2) мене більш чітко цікавить, як використання декількох файлів для виконання тих же завдань, які можна виконати в одному файлі, впливає на продуктивність системи.

Використання API Zend Form vs простого HTML

Я використовував API Zend Form з останніми та найкращими сучасними методами ОО, щоб створити HTML-форму з валідацією, перетворившись POSTв об'єкти домену.

На це мені знадобилося 35 файлів.

35 files = 
    = 10 fieldsets x {programmatic fieldset + fieldset manager + view template} 
    + a few supporting files

Все це можна замінити кількома простими HTML + PHP + JS + CSS файлами, можливо, всього 4 легкими файлами.

Чи краще? Чи варто? ... Уявіть, що ви завантажуєте 35 файлів + численні файли бібліотек Zend Zramework, які змушують їх працювати проти 4 простих файлів.


1
Дивовижне питання. Я зроблю деякий бенчмаркінг (візьміть мене на день або близько того, щоб знайти хороші випадки). Однак підвищення швидкості в цьому ступені приходить до величезних витрат на читабельність та витрати на розробку. Моя початкова здогадка - результат незначні підвищення продуктивності.
Ден Сабін

7
@Dan: Чи можете ви, будь ласка, помістити його у свій календар для порівняння коду після 1, 5 та 10 років технічного обслуговування. Якщо я пам’ятаю, я ще раз перевірю їх результати :)
mattnz

1
Так, це справжній кікер. Я думаю, що ми всі погоджуємось робити менше пошуків та викликів функцій швидше. Однак я не можу уявити собі випадок, коли це було віддано перевагу таким речам, як ретельно та легко навчати нових членів команди.
Ден Сабін

2
Щоб уточнити, чи є у вас конкретні вимоги щодо швидкості для вашого проекту. Або ви просто хочете, щоб ваш код "ішов швидше!" Якщо це останнє, я б не переживав про це, код, який досить швидкий, але простий у обслуговуванні, набагато кращий, ніж код, який є швидшим, ніж досить швидким, але безлад.
Cormac Mulhall

4
Ідея уникнути функціональних викликів з міркувань продуктивності - саме така дурне мислення, на яке Дійкстра виступив у своїй відомій цитаті про дозрілу оптимізацію. Серйозно, я не можу
RibaldEddie

Відповіді:


10

Моє запитання таке: коли виклик зводиться до машинного коду, до 1s і 0s, до інструкцій по збірці, чи повинен я взагалі турбуватися про те, що мій розділений за класом код з різноманітними функціями від дрібних до крихітних створює занадто багато зайвих витрат?

Моя відповідь - так, ви повинні. Не тому, що у вас є безліч маленьких функцій (колись накладні витрати функцій викликів були досить значними, і ви могли сповільнити свою програму, зробивши мільйон маленьких дзвінків в циклі, але сьогодні компілятори вкладуть їх за вас і те, що залишилося, буде взято опікуйтесь алгоритмами прогнозування процесорних процесорів, тому не хвилюйтеся з цього приводу), а тому, що ви введете в свої програми концепцію багатошаровості, коли функціональність занадто мала, щоб чітко балакати в голові. Якщо у вас є більші компоненти, ви можете бути впевнені, що вони не виконують однакову роботу знову і знову, але ви можете зробити свою програму настільки детально деталізованою, що, можливо, ви не зможете зрозуміти шляхи викликів, і в кінцевому підсумку щось що ледве працює (і ледве підтримується).

Наприклад, я працював у місці, яке показало мені довідковий проект для веб-сервісу 1 методом. Проект складався з 32 файлів .cs - для однієї веб-служби! Я подумав, що це занадто велика складність, хоча кожна частина була крихітною і легко зрозуміла сама, коли справа доходила до опису загальної системи, я швидко виявив, що мені потрібно простежити виклики, щоб побачити, що, до біса, це робило (там також було задіяно занадто багато абстракцій, як ви й очікували). Моєю заміною веб-сервісу було 4 .cs файли.

Я не вимірював продуктивність, як я вважаю, це було б приблизно однаково в цілому, але я можу гарантувати, що моє було значно дешевше в обслуговуванні. Коли всі говорять про те, що час програміста є більш важливим, ніж час процесора, тоді створюйте складні монстри, які коштують багато програмного часу як на розробці, так і на технічному обслуговуванні, вам доведеться дивуватися, що вони виправдовуються за погану поведінку.

Десь було сказано (цитуйте TBD), що до 70% всього коду складається з інструкції MOV ASM - завантаження регістрів процесора з належними змінними, а не власне обчислення.

Саме це і робить процесори, вони переміщують біти з пам'яті до регістрів, додають або віднімають їх, а потім повертають їх назад у пам'ять. Всі обчислення зводиться до цього. Зауважте, я колись мав дуже багатопоточну програму, яка витрачала більшу частину свого часу на зміну контексту (тобто збереження та відновлення стану реєстру потоків), ніж це працювало над кодовим потоком. Простий замок у неправильному місці справді накрутив продуктивність, і це був такий нешкідливий біт коду.

Тож моя порада: знайти розумний середину між будь-якими крайніми, які дозволять вашому коду добре виглядати для інших людей, і протестуйте систему, щоб перевірити, чи працює вона добре. Скористайтеся функціями ОС, щоб переконатися, що вона працює так, як ви очікували з процесора, пам'яті, диска та мережевого вводу-виводу.


Я думаю, що це зараз мені найбільше говорить. Мені напрошується думка, що хорошим середнім принципом є почати з концепцій відображення розуму для кодування фрагментів при дотриманні та використанні певних понять (наприклад, DI), а не зв'язуватися з езотеричним розкладанням коду та збитком (тобто, DI, все, що вам потрібно це чи ні).
Денніс

1
Особисто я вважаю, що більш "сучасний" "код дещо простіший у профілі ... Я думаю, що більш ремонтований фрагмент коду - це також простіше профілювати, але є обмеження, коли розбивання речей на невеликі шматочки робить їх менш
ремонтований

25

Без особливої ​​обережності така мікрооптимізація, як ці проблеми, призводить до неможливого коду.

Спочатку це виглядає як гарна ідея, профілер повідомляє вам, що код швидший, а V&V / Test / QA навіть каже, що працює. Незабаром виявляються помилки, змінюються вимоги та надсилаються вдосконалення, які ніколи не враховувалися.

Протягом життя проектний код деградує та стає менш ефективним. Обслуговуваний код стане ефективнішим, ніж його нездійснений аналог, оскільки він погіршиться повільніше. Причина - ентропія побудови коду під час її зміни -

Незбагненний код швидко має більше мертвого коду, зайві шляхи та дублювання. Це призводить до появи більше помилок, створюючи цикл деградації коду - включаючи його продуктивність. Недовго розробники мали низьку впевненість, що зміни, які вони вносять, є правильними. Це уповільнює їх, робить обережними і, як правило, призводить до ще більшої ентропії, оскільки вони стосуються лише деталей, які вони можуть бачити

Підтримуваний код, малі модулі та модульні тести легше змінювати, код, який більше не потрібен, простіше визначити та видалити. Код, який порушено, також простіше визначити, його можна відремонтувати або замінити впевнено.

Отже, врешті-решт, це зводиться до управління життєвим циклом і не є таким простим, як "це швидше, тому воно завжди буде швидше".

Перш за все, повільний правильний код нескінченно швидший, ніж швидкий неправильний код.


Дякую. Щоб трохи загнати це до того, куди я йшов, я не говорю про мікрооптимізацію, а про глобальний крок до написання меншого коду, що включає більше введення залежності і, отже, більше зовнішніх функціональних фрагментів, і більше "рухомих частин" коду взагалі що всі вони повинні бути з'єднані разом, щоб вони працювали. Я схиляюсь до думки, що це створює більше з'єднань / з'єднувачів / змінних проходів / MOV / PUSH / POP / CALL / JMP пух на апаратному рівні. Я також бачу значення в переході до читабельності коду, хоча за рахунок жертвувати обчислювальні цикли на апаратному рівні для "пуху".
Денніс

10
Уникнення функціональних викликів з міркувань продуктивності - це абсолютно мікрооптимізація! Серйозно. Я не можу придумати кращого прикладу мікрооптимізації. Які докази у вас є, що різниця в продуктивності насправді має значення для того програмного забезпечення, яке ви пишете? Це здається, що у вас немає.
RibaldEddie

17

Наскільки я розумію, як ви вказуєте в inline, для форм нижчого рівня коду, таких як C ++, це може змінити свою ситуацію, але я можу сказати, що МОЖУ злегка.

Веб-сайт підводить це підсумки - відповіді немає просто . Це залежить від системи, це залежить від того, що робить ваша програма, це залежить від мови, це залежить від компілятора та оптимізації.

Наприклад, C ++, inline може підвищити продуктивність. Багато разів це може нічого не робити або, можливо, знижувати продуктивність, але я особисто ніколи не стикався з цим, хоча й чув про історії. Inline - це не що інше, як пропозиція компілятору щодо оптимізації, яку можна ігнорувати.

Швидше за все, якщо ви розробляєте програми вищого рівня, накладні витрати не повинні викликати занепокоєння, якщо вони взагалі існують. Компілятори в наші дні надзвичайно розумні і в будь-якому разі повинні впоратися з цими речами. У багатьох програмістів існує код "жити": ніколи не довіряйте компілятору. Якщо це стосується вас, то навіть незначні оптимізації, які ви вважаєте важливими, можуть бути важливими. Але майте на увазі, кожна мова відрізняється в цьому плані. Java виконує вбудовані оптимізації автоматично під час виконання. У Javascript вбудована сторінка для веб-сторінки (на відміну від окремих файлів) - це поштовх, і кожна мілісекунда веб-сторінки може зараховуватись, але це більше проблеми з IO.

Але в програмах нижчого рівня, де програміст може багато працювати з машинним кодом разом із чимось на зразок C ++, підслуховування може змінити значення. Ігри - хороший приклад, коли конвеєрний процес CPU має вирішальне значення, особливо на консолях, і щось на зразок inline може трохи додавати тут і там.

Гарне прочитання в inline конкретно: http://www.gotw.ca/gotw/033.htm


Виходячи з точки зору мого питання, я не так зосереджений на вбудованому дизайні per seh, а на "кодифікованому проводці", яке займає процесорний процес, шину "та час введення / виводу часу, пов'язуючи різні фрагменти коду. Цікаво, чи є якийсь момент, коли є 50% або більше коду проводки та 50% фактичного коду, який ви хотіли запустити. Я думаю, що багато пуху навіть у найкруткішому коді, який можна написати, і це, здається, є фактом життя. Більшість фактичного коду, який працює на бітах і байтах, - це логістика - переміщення значень з одного місця в інше, стрибки на те чи інше місце, і лише іноді роблячи додавання ..
Денніс

... віднімання чи інші функції, пов'язані з бізнесом. Так само, як розгортання циклу може пришвидшити деякі цикли за рахунок менших накладних витрат, що виділяються для збільшення змінних, написання більших функцій, ймовірно, може додати деяку швидкість, за умови, що для цього встановлено ваш випадок використання. Моє занепокоєння тут більш загальне: бачення безлічі порад писати менші шматочки коду збільшує цю проводку, при цьому виграючи додавання читабельності (сподіваємось) та за рахунок роздуття на мікрорівні.
Денніс

3
@Dennis - одне, що слід враховувати, це те, що в мові ОО може бути ДУЖЕ мало співвідношення між тим, що пише програміст (a + b), і тим, що генерується код (просте додавання двох регістрів? Перше переміщується з пам'яті? Кастинг, потім функція викликає оператора об'єкта +?). Тож "малі функції" на рівні програміста можуть бути будь-якими, але невеликими, колись переведені в машинний код.
Майкл Коне

2
@Dennis Я можу сказати, що написання коду ASM (безпосередньо, не компільований) для Windows йде за рядками "mov, mov, invoke, mov, mov, call". Якщо виклик є макросом, який виконує виклик, завершений натисканням / вискакуванням ... Іноді ви виконуватимете функціональні дзвінки у своєму власному коді, але він оснащений усіма викликами ОС.
Брайан Ноблеуч

12

Це зміна порівняно зі "старим" або "поганим" кодовим практикою, де у вас є методи, що охоплюють 2500 рядків, і великі класи роблять все.

Я не думаю, що ніхто ніколи не думав зробити це гарною практикою. І я сумніваюся, що люди це зробили з міркувань виступу.

Я думаю, що тут відома цитата Дональда Кнута:

Слід забути про невелику ефективність, скажімо, про 97% часу: передчасна оптимізація - корінь усього зла.

Отже, у 97% вашого коду просто використовуйте добрі практики, пишіть маленькі методи (наскільки мало питання, я не думаю, що всі методи мають бути лише кількома рядками) тощо. Для решти 3%, де продуктивність має значення, виміряйте . І якщо вимірювання показують, що наявність багатьох маленьких методів насправді значно уповільнює ваш код, то вам слід об'єднати їх у більші методи. Але не пишіть нездійсненний код лише тому, що це може бути швидше.


11

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

На мій досвід, ось що призводить до уповільнення, і вони не малі. Вони є порядками:

  • Припущення, що будь-який рядок коду займає приблизно стільки ж часу, скільки будь-який інший. Наприклад, cout << endlпроти a = b + c. Перший займає в тисячі разів довше, ніж другий. У Stackexchange є багато питань форми "Я пробував різні способи оптимізації цього коду, але, схоже, це не має значення, чому б і ні?" коли в середині є великий-старий виклик функції.

  • Припущення про те, що будь-який виклик функції або методу після написання, звичайно, є необхідним. Функції та методи легко телефонувати, а дзвінки зазвичай досить ефективні. Проблема в тому, що вони схожі на кредитні картки. Вони спокушають витратити більше, ніж ти справді хочеш, і, як правило, приховують те, що ти витратив. Крім того, велике програмне забезпечення має шари на шарах абстракції, тому навіть якщо на кожному шарі є лише 15% відходів, понад 5 шарів, що з'єднує коефіцієнт уповільнення 2. Відповідь на це не у видаленні функціональності чи запису Більш великих функцій, це дисциплінувати себе бути на варті цієї проблеми і бути готовим і здатним вирішити її .

  • Галопуюча загальність. Цінність абстракції полягає в тому, що вона може дозволити вам більше робити з меншим кодом - принаймні, на це є надія. Цю ідею можна підштовхнути до крайнощів. Проблема із занадто великою загальністю полягає в тому, що кожна проблема є специфічною, і коли ви вирішуєте її загальними абстракціями, ці абстракції не обов'язково можуть використовувати конкретні властивості вашої проблеми. Наприклад, я бачив ситуацію, коли використовувався фантазійний клас черги пріоритету, який міг бути ефективним при великих розмірах, коли довжина ніколи не перевищувала 3!

  • Галопуюча структура даних. OOP є дуже корисною парадигмою, але вона не заохочує мінімізувати структуру даних, скоріше, це спонукає спробувати приховати її складність. Наприклад, існує поняття "сповіщення", де, якщо дата A якимось чином модифікована, A видає подію сповіщення, щоб B і C також могли змінювати себе так, щоб весь ансамбль був узгодженим. Це може поширюватися на багатьох шарах та значно збільшувати вартість модифікації. Тоді цілком можливо, що незабаром після скасування зміни на A можуть бути скасованіабо змінився на ще одну модифікацію, що означає, що зусилля, витрачені на намагання зберегти послідовність ансамблю, потрібно докласти ще раз. Помилково, що це ймовірність помилок у всіх цих обробниках сповіщень, а також циркулярність тощо. Набагато краще спробувати зберегти структуру даних нормалізованою, так що будь-які зміни потрібно проводити лише в одному місці. Якщо ненормованих даних неможливо уникнути, краще мати періодичні пропуски для виправлення невідповідності, а не робити вигляд, що вони можуть бути узгоджені з коротким повідцем.

... коли я придумаю більше, я додам його.


8

Коротка відповідь - «так». І, як правило, код буде трохи повільніше.

Але іноді правильний рефакторинг OO-пошуку виявить оптимізацію, яка зробить код швидшим. Я працював над одним проектом, де ми створили складний алгоритм Java набагато більше OO-ish, з належними структурами даних, геттерами тощо замість брудних вкладених масивів об’єктів. Але, покращивши ізоляцію та обмеживши доступ до структур даних, нам вдалося перейти від гігантських масивів подвійних (з нулями для порожніх результатів) до більш організованих масивів пар, з NaN для порожніх результатів. Це дало 10-кратне збільшення швидкості.

Додаток: Загалом, менший, краще структурований код повинен бути більш прихильним до багатопотокових, ваш найкращий спосіб отримати основні прискорення.


1
Я не бачу, чому для переходу з Doubles на doubles потрібен краще структурований код.
svick

Нічого собі, голова? Оригінальний код клієнта не мав справу з Double.NaNs, але перевіряв наявність нулів порожніх значень. Після реструктуризації ми могли б впоратися з цим (за допомогою інкапсуляції) за допомогою одержувачів різних результатів алгоритму. Звичайно, ми могли переписати клієнтський код, але це було простіше.
user949300

Для запису я не був тим, хто спротив цю відповідь.
svick

7

Крім усього іншого, програмування стосується компромісів . Виходячи з цього факту, я схильний відповісти "так", це може бути повільніше. Але подумайте, що ви отримаєте натомість. Ознайомлення з читабельним, багаторазовим і легко змінюваним кодом легко перевершує будь-які можливі недоліки.

Як @ user949300 згадувалося , що легше визначити області , які можуть бути алгоритмічний або архітектурно поліпшеною з таким підходом; вдосконалення таких, як правило, набагато вигідніше та ефективніше, ніж відсутність можливих OO або функціональних викликів (що, це, мабуть, лише шум).


Я також уявляю, що хороші оптимізації можна зробити ASM ще до того, як він фактично працює на апаратному забезпеченні.

Кожного разу, коли щось подібне перетинає мою думку, я пам’ятаю, що десятиліття, проведені найрозумнішими людьми, що працюють над компіляторами, ймовірно, роблять такі інструменти, як GCC, набагато кращі за мене при створенні машинного коду. Якщо ви не працюєте над якимись мікроконтролерами, я пропоную вам не турбуватися про це.

Я припускаю, що додавання дедалі більшої кількості функцій і все більше об'єктів і класів у коді призведе до проходження все більшої кількості параметрів між меншими фрагментами коду.

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


Підсумувати все це; нехай компілятор виконує свою роботу, зосередиться на важливих речах, таких як вдосконалення алгоритмів та структур даних. Усі зразки, про які ви згадали у своєму запитанні, існують, щоб допомогти вам у використанні.

PS: Ці 2 переговори Крокфорда вискакували мені в голову, і я думаю, що вони дещо пов'язані. Перший - це суперкоротка історія CS (яку завжди добре знати з будь-якої точної науки); друге - про те, чому ми відкидаємо хороші речі.


Мені дуже подобається ця відповідь. Люди жахливі, коли вдруге відгадують компілятора і роблять знімки там, де вузькі місця. звичайно, велика часова складність - це те, про що слід пам’ятати, але великий 100-рядовий метод спагетті проти виклику фабричного методу + якийсь віртуальний метод диспетчеризації ніколи не повинен бути навіть дискусією. Виступ у цьому випадку зовсім не цікавий. також великий плюс зауважити, що "оптимізація" без важких фактів та вимірювань - це марна трата часу.
сара

4

Я вважаю, що тенденції, які ви ототожнюєте, вказують на правду щодо розробки програмного забезпечення - час програміста дорожче, ніж час процесора. Поки що комп’ютери стали лише швидше і дешевше, але змішана безладдя програми може зайняти сотні, якщо не тисячі людино-годин, щоб змінити. Зважаючи на витрати на зарплату, пільги, службові приміщення тощо, тощо, економічно вигідніше мати код, який може виконуватись трохи повільніше, але змінювати його швидше та безпечніше.


1
Я погоджуюся, але мобільний пристрій стає настільки популярним, що, на мою думку, вони є великим винятком. Хоча потужність обробки збільшується, ви не можете створити додаток для iPhone і очікуєте, що зможете додавати пам'ять, як можна, на веб-сервері.
JeffO

3
@JeffO: Я не погоджуюсь - мобільні пристрої з чотирьохядерними процесорами зараз нормальні, продуктивність (особливо, оскільки це впливає на час автономної роботи), хоча турбується, менш важлива, ніж стабільність. Повільний мобільний телефон або планшет отримує погані відгуки, той, який нестабільний, забивається. Мобільні додатки дуже динамічні, змінюються майже щодня - джерело має йти в ногу.
mattnz

1
@JeffO: Для чого це варто, віртуальна машина, яка використовується на Android, дуже погана. За критеріями, це може бути на порядок повільніше, ніж нативний код (в той час як найкраща в породі зазвичай просто трохи повільніше). Вгадайте, що, нікого не цікавить. Написання програми швидко, і процесор сидить там, закручуючи великі пальці і все одно чекаючи введення користувачем 90% часу.
Ян Худек

Ефективність є більшою, ніж необроблені орієнтири процесора. Мій телефон Android працює нормально, за винятком випадків, коли AV сканує оновлення, і тоді, здається, він просто зависає довше, ніж мені подобається, і його чотириядерна 2 Гб оперативна пам'ять! Сьогодні пропускна здатність (чи мережа, чи пам'ять) - це, мабуть, головне вузьке місце. Ваш надшвидкий процесор, ймовірно, обертає великі пальці 99% часу, і загальний досвід все ще поганий.
gbjbaanb

4

Добре 20 років тому, що, напевно, ви не називаєте новим, а не «старим чи поганим», було правило тримати функції, малі для розміщення на друкованій сторінці. Тоді у нас були матричні матричні принтери, тож кількість рядків була дещо фіксованою, як правило, лише один або два варіанти для кількості рядків на сторінку ... напевно менше 2500 рядків.

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

Що стосується коду, створеного компілятором (машинний код, якщо ви хочете), то чим більше функція, тим більше можливостей для необхідності розливати проміжні значення в регістри до стеку. Коли використовуються рамки для стеків, витрата стека є більшими шматками. Чим менше функцій, тим більше можливостей для даних, що більше залишаються в регістрах, і менше залежність від стека. Малі шматки стека необхідні для функції природно. Рами стеків мають плюси і мінуси для продуктивності. Більш дрібні функції означають більше налаштування та очищення функцій. Звичайно, це також залежить від того, як ви компілюєте, які можливості ви надаєте компілятору. У вас може бути 250 10 лінійних функцій замість однієї 2500 лінійної функції, одна 2500 лінійна функція, яку отримає компілятор, якщо зможе / вирішить оптимізувати в цілому. Але якщо взяти ці 250 10-ти рядкових функцій і розподілити їх по 2, 3, 4, 250 окремих файлів, скласти кожен файл окремо, тоді компілятор не зможе оптимізувати майже стільки ж мертвого коду, скільки він може мати. Підсумок тут - є плюси і мінуси для обох, і неможливо встановити загальне правило щодо того чи іншого, це найкращий спосіб.

Функції розумного розміру - те, що людина може бачити на екрані чи сторінці (розумним шрифтом) - це те, що вони можуть споживати краще та зрозуміти, ніж досить великий код. Але якщо це лише невелика функція з дзвінками до багатьох інших невеликих функцій, які викликають багато інших невеликих функцій, вам потрібно кілька вікон або браузерів, щоб зрозуміти цей код, щоб ви нічого не купували з боку читабельності.

Unix спосіб полягає в тому, щоб використовувати мій термін, добре відшліфовані блоки лего. Навіщо ви використовували функцію стрічки через багато років після того, як ми перестали використовувати стрічки? Оскільки блоб зробив свою роботу дуже добре, і на зворотному боці ми можемо замінити стрічковий інтерфейс файловим інтерфейсом і скористатися м'ясом програми. Навіщо переписувати програмне забезпечення, що записує компакт-диск, просто тому, що scsi пішла, коли домінуючий інтерфейс замінили на ide (щоб потім повернутися пізніше). Знову візьміть реванш полірованих підблоків та замініть один кінець новим інтерфейсним блоком (також зрозумійте, що дизайнери обладнання просто застосували блок інтерфейсу на конструкціях апаратних засобів, щоб у деяких випадках зробити привід scsi мати інтерфейс для ідеї scsi. Щоб скоротити це , створити поліровані блоки лего розумного розміру, кожен з чітко визначеною метою та чітко визначеними входами та виходами. ви можете обернути тести навколо цих блоків лего, а потім взяти один і той же блок і обернути інтерфейс користувача та інтерфейси операційної системи навколо того ж блоку та блоку, теоретично добре перевірений і добре зрозумілий, не потрібно налагоджувати, лише додані додаткові нові блоки з кожного кінця. якщо всі інтерфейси ваших блоків добре розроблені, а функціональність добре зрозуміла, ви можете створити безліч речей з мінімальним, якщо будь-яким клеєм. так само, як із синіми, червоними та чорно-жовтими блоками лего відомих розмірів та форм, ви можете зробити дуже багато речей. якщо всі інтерфейси ваших блоків добре розроблені, а функціональність добре зрозуміла, ви можете створити безліч речей з мінімальним, якщо будь-яким клеєм. так само, як із синіми, червоними та чорно-жовтими блоками лего відомих розмірів та форм, ви можете зробити дуже багато речей. якщо всі інтерфейси ваших блоків добре розроблені, а функціональність добре зрозуміла, ви можете створити безліч речей з мінімальним, якщо будь-яким клеєм. так само, як із синіми, червоними та чорно-жовтими блоками лего відомих розмірів та форм, ви можете зробити дуже багато речей.

Кожна людина різна, їх визначення відшліфованого та чітко визначеного та перевіреного та читаного різниться. Наприклад, нерозумно, наприклад, професор диктує правила програмування не тому, що вони можуть бути або не можуть бути поганими для вас як для професіонала, але в деяких випадках полегшити роботу з читання та класифікації вашого коду професору чи асистентам студента ... Ви з однаковою ймовірністю професійно виявите, що кожна робота може мати різні правила з різних причин, як правило, один чи кілька людей у ​​владі мають свою думку щодо чогось, правильно чи неправильно, і з цією владою вони можуть диктувати, що ви робите це там (або кинути, звільнити, або якось прийти до влади). Ці правила настільки ж часто ґрунтуються на думці, як і в деяких фактах про читанність, ефективність, перевірити, переносимість.


"Таким чином, ви повинні знайти рівень свого комфорту, який може і змінюватись для кожного програміста". Я думаю, що рівень оптимізації повинен визначатися вимогами до продуктивності кожного фрагмента коду, а не тим, що подобається кожному програмісту.
svick

Компілятор впевнений, якщо припустити, що програміст сказав компілятору оптимізувати компілятори, як правило, не оптимізувати (у командному рядку IDE може бути інший за замовчуванням, якщо використовується). Але програміст може не знати достатньо, щоб оптимізувати розмір функції і з якомога більшою кількістю цілей, де це стає марним? Ефективність налаштування вручну має тенденцію негативно впливати на читабельність, а зокрема ремонтопридатність, а також тестуваність може йти в будь-який бік.
old_timer

2

Залежить від того, наскільки розумний ваш компілятор. Як правило, спроба перехитрити оптимізатор - це погана ідея і може фактично позбавити компілятора можливостей для оптимізації. Для початку, ви, мабуть, не маєте жодного поняття, що він може зробити, і більшість того, що ви робите, насправді впливає на те, наскільки добре це робиться.

Передчасна оптимізація - це поняття, яке намагаються зробити програмісти, і закінчується важким для підтримки кодом, який насправді не був на критичному шляху того, що вони намагалися зробити. Намагаюся вичавити якомога більше процесора, коли більшість часу ваша програма фактично заблокована в очікуванні подій IO - це те, що я багато бачу, наприклад.

Найкраще - кодувати правильність і використовувати профілер для пошуку фактичних вузьких місць та виправити їх, проаналізувавши, що знаходиться на критичному шляху та чи можна його покращити. Часто кілька простих виправлень проходять довгий шлях.


0

[Елемент комп’ютер-час]

Чи впливає переналагодження на швидше з’єднання та менші функції на швидкість коду?

Так. Але перекладачі, компілятори та JIT-компілятори залежать від того, щоб зняти цей код «шва / проводки», а деякі роблять це краще, ніж інші, але деякі ні.

Проблема з декількома файлами додає до накладних витрат введення / виводу, так що досить швидко впливає і на швидкість (у комп'ютерний час).

[елемент швидкості для людини]

(А я повинен дбати?)

ні, вам не варто байдуже. У цей час комп’ютери та мікросхеми є досить швидкими, і інші чинники переймаються такими, як затримка мережі, введення / виведення бази даних та кешування.

Отже, уповільнення 2x - 4x у виконанні власного коду часто буде заглушене тими іншими факторами.

Що стосується завантаження декількох файлів, про це часто піклуються різні рішення кешування. Може знадобитися більше часу для завантаження речей та їх об’єднання в перший раз, але для кожного наступного разу для статичних файлів кешування працює так, ніби завантажується один файл. Кешування - це рішення для завантаження кількох файлів.


0

ВІДПОВІДЬ (у випадку, якщо ви пропустили його)

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

Коротко

Не дбайте про продуктивність

У контексті запитання про це вже піклуються розумніші упорядники та перекладачі

Потурбуйтеся про написання коду, що підтримується

Код, коли витрати на обслуговування знаходяться на рівні розумного розуміння людиною. тобто не пишіть 1000 менших функцій, роблячи код незрозумілим, навіть якщо ви розумієте кожну з них, і не пишіть 1 об'єктну функцію бога, яка занадто велика для розуміння, але записуйте 10 добре розроблених функцій, які мають сенс для людини, і є простий в обслуговуванні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.