Comments 94
sortRoutine
получается в пять экранов длиной.Что-нибудь такое, где вклад памяти небольшой, а нагрузка идет в основном на вычислительные блоки.
Как насчет численного решения диффуров, например? Или рейтрейсер какой-нибудь. Да хотя бы примитивное сложение 32 битных слов как в недавнем посте схожей тематики. Там, кстати, компилятор победил довольно уверенно.
Понятно, что работа с памятью будет в любом случае. Ну пусть её будет не 75% как в случае quicksort, а хотя бы процентов 20.
И тут пришёл какой-то чувак, написавший решение не через суммирование F[n+1] = F[n]+F[n-1], а через возведение матрицы ((1,1),(0,1)) в степень. Чуть ли не на бейсике. И, соответственно, порвавший всех.
И тут пришёл какой-то чувак, написавший решение не через суммирование F[n+1] = F[n]+F[n-1], а через возведение матрицы ((1,1),(0,1)) в степень. Чуть ли не на бейсике. И, соответственно, порвавший всех.
И получил фигню, потому что такое возведение считает не то? Ибо возведение такой матрицы в степень n даст ((1,n),(0,1)).
Как нам говорит википедия (https://en.wikipedia.org/wiki/Fibonacci_number), правильная матрица ((1,1),(1,0)). Соответственно, для вычисления F[N] нам достаточно быстрого возведения матриц в степень. Очевидно, что существует алгоритм с логарифмической сложностью. Таким образом алгоритмы с меньшей эффективностью для достаточно большого N покажут результат хуже.
При желании можно заняться оптимизацией возведения в степень, ибо логарифмический алгоритм не является лучшим, хоть и тривиально реализуется.
И получил фигню, потому что такое возведение считает не то? Ибо возведение такой матрицы в степень n даст ((1,n),(0,1)).
Попробуйте ловить суть, а не цепляться к мелочам.
Существует алгоритм со сложностью O(log N) — это факт, а то, что с индексами ошибка — это не критично, любой умеющий думать человек её тут же поправит.
Попробуйте не использовать ad hominem. Я писал «алгоритм» быстрого возведения в степень (не для матриц, в другом кольце), но сути это не меняет.
Я лишь про то, что контроль более тупым и прямолинейным алгоритмом вполне полезен.
Единственное — из-за длинной арифметики там вроде не O(log(n)), а поболее получалось (но и алгоритмы «в лоб» тоже были не O(n)).
Сложение двух N-разрядных чисел делается за (опять же с точностью до константы) N операций, так что реализация через сложение потребует O((1 + 2) + (2 + 3) +… + (N — 1 + N)) = O(N^2) операций.
Если делать умножение длинных чисел наивным способом (за квадрат от числа разрядов), то нам понадобится O(1^2 + 2^2 + 4^2 +… + (N/2)^2) = O(N^2) операций. Правда константа получается куда лучше.
Ну и если делать более интеллектуальное длинное умножение, то и асимтотика получится лучше.
А ту устроенную мной развлекуху люди еще помнят.
PS. Работы с памятью там как раз много, это вообще основное. Числа длинные (килобайты и десятки килобайт), вам нужно пословно, с переносами, их складывать. Я, например, сопроцессором складывал, там внутре есть/был хитрый режим 80-битных целых, до которого можно только из ассемблера дотянуться. Но вы понимаете, что даже в этом случае на 1 операцию сложения, которая вообще плевая, приходится две операции чтения и одна — записи.
А дотягивался я до туда ни из какого не ассемблера, а из Фортрана.
хитрый режим 80-битных целых
Расскажите поподробнее, в документации по x87 такого нет. Есть только режим 64-битных целых (=extended без мантиссы) и режим BCD-чисел (72 бита + знак), который имеет более узкий диапазон, чем int64.
Раньше, когда сопроцессор был именно отдельным процессором, наверное, можно было параллельно что-нибудь считать на CPU, пока FPU выполняет вычисление. Потому же сопроцессор стал частью CPU. Жалко, я не застал этого момента: первый компьютер сопроцессора ещё не имел, второй — уже не имел.
и режим BCD-чисел (72 бита + знак), который имеет более узкий диапазон, чем int64
Но int64 будет за две операции вычисляться — add и adc.
Там трюк в том, что в конце работы результат нужно вывести в ascii. Для числа длиной десятки килобайт перевод из двоичной формы в десятичную оказывается достаточно медленной операцией (нужно делить, хотя бы и сдвигами, но всё равно).
sorttest.zip
во времена GitHub :(
Даже на AVX дает прирост больше чем в 50% при рандомной альфа-маске.
Возможно, я не идеально знаю ключи для оптимизации, компилил при помощи gcc -O3
На AVX2 ассемблер еще быстрее.
Я уже не говорю про то, что он отрабатывает ЗНАЧИТЕЛЬНО быстрее, если в маске у нас 16 следующих друг за другом 0x00 или 0xFF (полностью прозрачный или полностью непрозрачный фрейм), что очень актуально при наложении эффектов типа логотипа.
Ну так вот. Я написал код на ассемблере с использованием SSE2. Вроде быстро.
Но потом я переписывал это код на AMD64 и решил отказаться от ассемблера. Переписал на интринсики. При чем вообще не заморачивался на скорость. Писал грубо, без каких-либо оптимизаций. Там присутствовали конструкции вида:
_mm_storeu_si128( ( __m128i * )dst_argb, _mm_packus_epi16( _mm_add_epi16(
_mm_mulhi_epu16( _mm_shuffle_epi8( t4, pta1 ), _mm_shuffle_epi8( tt5, a1l ) ),
_mm_mulhi_epu16( _mm_shuffle_epi8( tt5, a1r ), t6 ) ),
_mm_add_epi16( _mm_mulhi_epu16( _mm_shuffle_epi8( t4, pta2 ), _mm_shuffle_epi8( tt5, a2l )), _mm_mulhi_epu16( _mm_shuffle_epi8( tt5, a2r ), t6 ) ) ) );
Ну т.е. многоэтажные вызовы интринсиков. И каков результат? А результат таков, что этот код стал работать быстрее того, что был на голом ассемблере. Так что компиляторы нынче действительно очень хороши. Смысла в ассемблере очень мало.
Никто не будет писать отдельный код для каждого процессора, за очень редким исключением. Обычно вполне устраивает существенное ускорение программы за счёт использования векторных инструкций.
А приведенном коде (который кстати совсем не а C++), производится работа по сортировке массивов заданного типа, и именно это дает возможность переписать этот код на ассемблере с выигрыщем в произовдительности и (возможно) свободном от ошибок. А теперь представьте что у вас эти Item разные, имееют разные китерии сравнениия, да и вообще у вас не Item а например Peoples, или еще чтото… На C — вы будете пытаться это обобщить через void* и предикаты (и имеете шанс запутаться), на asm вы уже запутаетесь в косвенной адресации и предикатах, шаблонные же алгоритмы на C++ сгенирируют за вас вполне корректный код, который работает по фиксированным отступам в пямяти, со знанием размеров объектов и членов, именно по тому что КОМПИЛЯТОР ЗНАЕТ ТИП и именно это поможет быть этому коду оптимальным.
Забавно смотреть бенчи без описания окружения… Тем более в разных окружениях (здесь явно интересны результаты под разные процессоры и с разными опциями оптимизации).
Да и алгоритм тут не самый обычный (редко исполняется). В 80% случаев в коде идут простые переборы, хэши и вычисления, там уже можно использовать SIMD etc.
- sort_cpp_recurse (GCC) занимает 64мс
- sort_cpp_recurse (clang) занимает 62мс
- sort_asm_recurse занимает 60мс (и потребовало некоторое время на адаптацию к линуксовому ABI)
- std::sort занимает 55мс
Отсюда вывод: чем выпендриваться и писать на ассемблере, лучше взять готовый, переносимый, отлаженный код на C++
Если сравнивать результат по всем ста прогонам вместе, то sort_cpp_recurse (clang) даже слегка обгоняет sort_asm_recurse.
Слышал такое мнение, что окружение в котором запускается процесс не может заставить его выполняться быстрее. Т.е. если у вас есть 4-е запуска программы с разным временем выполнения, то тот, у которого это время меньше ближе всего к реальному времени исполнения, т.к. ему меньше всего «мешали»
И время на один прогон определяем как T\N, где T — общее время за вычетом P «прогревных» прогонов, B лучших и B худших. Так можно минимизировать ошибки и убрать джиттер во времени максимальным образом.
народ в интернете иногда может говорить нечто, совсем не соответствующее действительности.
Как вы мягко перевели. :)
Такие синтетические тесты типа: "Берем функцию, пишем на C и на ассемблере", совершенно не демонстрируют преимущества ассемблера. Я всегда утверждал, что сравнивать надо реальные, законченные программы, которые делают что то разумное и полезное.
Дело в том, что человек не пишет код на ассемблере так как пишет его на ЯВУ. Некоторые вещи, которые на ассемблере просты и логичны, на C такими не являются. Поэтому, неоптимизированные программы на ассемблере всегда получаются намного быстрее чем неоптимизированные программы на C/C++. Мои тесты (а я проводил такие несколько раз) показывают от 5 до 80 раз большее быстродействие ассемблерных программ.
Далее, если сделать несколько итерации оптимизации, программа на C/C++ можно довести до примерно 20..50% медленнее, чем та же программа на ассемблере. Но только если код на ассемблере известен.
В итоге, код на C получается совершенно нечитаемым, а код на ассемблере вполне читаемым.
Но какая часть программ оптимизируют так глубоко? И как поддерживается так глубоко оптимизированная программа?
В реальной жизни никто так делать не будет, так что такие тесты совершенно ничего не доказывают.
Никто? Ведь я именно так и делаю. И знаю многих, которые так делают. И поэтому такие тесты многое доказывают.
Можно конечно — и мою и не мою.
Не моя: RWASA — современный веб сервер, с TLS, быстрее nginx-а. Автор: Jeff Marrison из Австралии.
Моя: AsmBB — Простой форумный движок. Хорошо масштабируем, благодаря использования FastCGI протокола. (И FastCGI фреймворк к нем, которой можно использовать для других веб приложении).
Писать с нуля, конечно можно, но зачем? Это то же самое если писать с нуля на C (без libc и других библиотек). На ассемблере есть библиотеки, и повторное использование кода никто не запрещал. А когда пишешь все на ассемблере и соблюдаешь правила структурного программирования, то код постепенно накопляется и писать становится все легче и легче.
У меня почти весь этот код собран в библиотеке FreshLib, которая разрабатывается как часть и основа Fresh IDE. Там многое чего есть — от обработка строк и масивов, до OOP библиотека графического интерфейса (неокончена).
Поэтому, такие небольшие проекты как AsmBB пишутся быстро и приятно. Правда, несколько медленнее (примерно на 50%) чем на PHP, но и результат несравнимо лучше. Можно посмотреть история версии насчет реальные сроки разработки до v1.0;
а 24 часа на php можно написать немаленький проект, причём он будет с БД, кешированием, email-рассылками, полноценным логированием, и т.д.
Это так, только потому что некто уже все это написал и не за 24 часа, а намного медленнее.
То же самое и на ассемблере. Теперь каждый может взять мои исходники и за 24 часа написать чего нибудь, которое работает на FastCGI. Или например — парсер markdown можно написать за 10 минут. Как? А просто — его уже есть в FreshLib библиотеке.
А если что нибудь все еще не написано, то некто должен потратить некоторое время и не 24 часа.
Ну я же говорил о 1.5 раза, не знаю откуда x2 взялось. :D
А почему не работают на ассемблере? Это просто. Потому что пока действовал закон Мура, писать даже на 50% медленнее было смерти подобно. Всех учили писать быстро плохой код, ведь экспоненциальный прогресс исправить медленный код, а кто первый встанет, тому и тапочки.
Но дело в том, что экспоненциального прогресса уже не будет. Будет максимум линейный, да и то вряд ли. Темпы разработки несколько снизятся и рано или поздно писать все на ассемблере станет выгодным. Кстати, оно уже и так выгодно, только предрассудки все еще тормозят процесс.
Опять таки — почти всегда всё упирается в БД.
А что мешает написать БД на ассемблере? Вот, веб сервер уже написали и он быстрее самых быстрых серверов на C.
Разработка на нём — не в 2, и не в полтора, а в десятки раз медленнее и сложнее чем на языках высокого уровня.
Это только если программист не знает ассемблер, а знает ЯВУ. Если программист на одинаковом уровне знает ассемблер и ЯВУ, то мои тесты показывают совсем не в десятки раз медленнее, а именно в 1.5, ну пусть будет в 2 раза медленнее. Я ссылку давал наверх к таймлайне.
Ну так вот как раз таки БД — вероятно и будет смысл написать на ассемблере.
На самом деле нет.
Потому что ещё раньше, чем ассемблерщик закончит вылизывать код ради оптимизации производительности на процент — станет известен новый алгоритм, позволяющий увеличить производительность вдвое, и вылизанный код отправится в мусорное ведро.
В реальной жизни на ассемблере пишется либо очень низкоуровневый код (работа с устройствами), либо очень алгоритмически примитивный и при этом критический к производительности, типа
memcpy
.А кто вам сказал, что nginx — самый быстрый веб-вервер на С? Изначально у него была задача быть эффективным по памяти, про скорость там ничего нет.
А кто вам сказал, что nginx — самый быстрый веб-вервер на С? Изначально у него была задача быть эффективным по памяти, про скорость там ничего нет.
Ну, не знаю, так говорят. Я сам бенчмарки не ставлю.
Но RWASA также эффективнее по памяти. Да и все программы на ассемблере будут пожалуй лучше по памяти, чем эквивалентная программа на ЯВУ.
Потому что в ассемблере контролируется все на самом низком уровне.
рано или поздно писать все на ассемблере станет выгодным. Кстати, оно уже и так выгодноМожете про это подробнее?
sqlSelectConst text "select ? as slug, ? as caption, ? as source, ? as ticket, ? as tags" sqlGetQuote text "select U.nick, P.content from Posts P left join Users U on U.id = P.userID where P.id = ?" sqlInsertPost text "insert into Posts ( ThreadID, UserID, PostTime, Content, ReadCount) values (?, ?, strftime('%s','now'), ?, 0)"
Все SQL запросы писать руками?
xor eax, eax mov [.fPreview], eax mov [.slug], eax mov [.source], eax mov [.caption], eax mov [.tags], eax mov [.ticket], eax mov [.stmt], eax mov [.stmt2], eax
И обнуление переменых писать руками?
stdcall StrNew mov edi, eax ... stdcall StrDel, eax
И создавать и удалять тоже все надо руками?
stdcall StrCat, [esi+TSpecialParams.page_title], "Posting in: " stdcall StrCat, [esi+TSpecialParams.page_title], [.caption]
И вместо
$page_title = "Posting in: " . $caption
надо писать вот эту портянку? Или может копипастить из другого аналогичного места?Да банально на все вот это уйдет несравнимо больше времени, чем если писать на PHP. Потому что на PHP тут вообще почти ничего писать не надо. Кода будет меньше даже не в 1.5 раза, а раза в 3-4.
Далее в целом по коду.
cmp [esi+TSpecialParams.thread], 0 je .perm_ok mov eax, permPost .perm_ok: or eax, permAdmin
Ага, система проверки доступа захардкожена прямо здесь же. Какой там RBAC, битовых флагов хватит.
test eax, eax jz .title_ok .title_ok: ... test eax, eax jz .tags_ok tags_ok: ...
Вместо if-ов или цикла куча условных goto на три экрана.
stdcall StrByteUtf8, [.caption], 512
Какие-то магические константы. Наверно размер буфера. А может максимальная длина в символах. Кто его знает.
cinvoke sqlitePrepare_v2, [hMainDatabase], sqlGetThreadInfo, -1, eax, 0
Константа sqlGetThreadInfo где-то в другом месте находится, надо догадаться поискать ее в файле showthread.asm.
mov [.source], eax ; [.source] should be 0 at this point!!!
Угу, оставим себе напоминание, чтобы не запутаться в куче меток и переходов между ними.
; check the ticket stdcall CheckTicket, [.ticket], [esi+TSpecialParams.session] jc .error_bad_ticket ; begin transaction! lea eax, [.stmt] cinvoke sqlitePrepare_v2, [hMainDatabase], sqlBegin, sqlBegin.length, eax, 0 cinvoke sqliteStep, [.stmt]
Бизнес-логика тоже тут же, в контроллере обработки запроса. Правильно, зачем ее выносить, запутаемся же потом. И работу с БД сюда же поместим.
В общем, так даже на PHP уже не пишут, а вы про ассемблер.
Кода будет меньше даже не в 1.5 раза, а раза в 3-4.
А я никогда не говорил, что код на ассемблере будет больше в 1.5 раза. Почитайте мои посты наверх.
Бизнес-логика тоже тут же, в контроллере обработки запроса. Правильно, зачем ее выносить, запутаемся же потом. И работу с БД сюда же поместим.
Все что находится в файле source/post.asm
это только "бизнес логика" — запись в БГ, очередной постинг юзера. Конечно те процедуры вызываются как результат запроса POST, но сам запрос обрабатывается в другом месте.
Константа sqlGetThreadInfo где-то в другом месте находится, надо догадаться поискать ее в файле showthread.asm.
Ну не знаю, можно и погадать, конечно, но не лучше ли спросить у IDE? — Оно всегда знает где какая константа находится.
А я никогда не говорил, что код на ассемблере будет больше в 1.5 раза. Почитайте мои посты наверх.
Размер кода, который надо писать руками, напрямую связан со скоростью разработки, о которой вы говорили.
Все что находится в файле source/post.asm это только "бизнес логика"
Я понял, что post здесь не связан с HTTP-методом POST. В терминологии MVC, которая используется в PHP-фреймворках, post.asm это контроллер обработки запроса на создание сущности "Post". Бизнес-логика — это поведение сущности (в данном случае при ее создании). У вас она перемешана с редиректами и sql-запросами. Например, я хочу создавать post автоматически при некотором событии, как это сделать? Если я просто вызову PostUserMessage, то в случае ошибки он прервет текущий бизнес-процесс и сделает redirect на другую веб-страницу. А если я MySQL хочу использовать, вместо SQLite?
Ну не знаю, можно и погадать, конечно, но не лучше ли спросить у IDE?
Суть не столько в том, что IDE ее найдет, сколько в том, что это глобальный объект, находящийся даже не в конфиге, а где-то в специфичном файле. Это мне надо следить за порядком подключения файлов? И если я в другом файле определю константу с тем же именем, будет ошибка компиляции? С переменной hMainDatabase аналогично.
Размер кода, который надо писать руками, напрямую связан со скоростью разработки, о которой вы говорили.
Нет, не так. Программист не машинистка. Он прежде всего придумывает код, а потом пишет. А код на ассемблере придумывается быстрее если считать строк. Просто потому что эти строки придумываются одновременно по нескольких.
А если я MySQL хочу использовать, вместо SQLite?
В этом проекте, нельзя использовать другая БД кроме SQLite. Это не потому что на ассемблере, а потому что я так решил на стадия проектирования программы Если нужно было, то сделал бы по другому.
Нет, не так. Программист не машинистка.
Вот при увеличении размера кода он превращается в машинистку. На обдумывание остается меньше времени. К тому же при написании на ассемблере утомляемость больше, так как надо больше состояния держать в памяти — регистры, стек, глобальные переменные.
А код на ассемблере придумывается быстрее если считать строк. Просто потому что эти строки придумываются одновременно по нескольких.
Можете какой-нибудь пример привести? Свои я уже приводил. На ЯВУ в некоторых случаях количество строк уменьшается почти до нуля, соответственно, и придумывать ничего не надо. На ассемблере строки придумываются по несколько, но и каждая их них делает меньше действий. А еще их написать надо. Не говоря уже о том, что на ЯВУ тоже можно по несколько строк сразу придумывать.
В этом проекте, нельзя использовать другая БД кроме SQLite
То есть, это не столько движок, который можно подключить в существующий проект, сколько отдельное законченное приложение. Его даже в подпапку нельзя поставить, только в корень сайта. Как-то не тянет на большой современный масштабируемый проект)
В этом проекте, нельзя использовать другая БД кроме SQLite. Это не потому что на ассемблере, а потому что я так решил на стадия проектирования программы
В реальных проектах так не бывает — чтобы на стадии проектирования сразу было понятно, какие компоненты потребуются, а какие нет.
Я не спорю, что в качестве хобби написать веб-сервер с форумным движком можно хоть на Алголе, хоть на Brainfuck. Но к написанию «разумных, полезных (кому-то, кроме самого автора), законченных программ» всё это отношения не имеет.
Но к написанию «разумных, полезных (кому-то, кроме самого автора), законченных программ» всё это отношения не имеет.
Весь домен flatassembler.net сейчас хостится на сервер RWASA. Никакие проблемы не возникали с момента перемещения. Даже стало лучше — раньше были проблемы с https, теперь уже нет. Раньше были проблемы с производительности. Теперь уже нет.
AsmBB, совершенно работающая система малого форума, на которой можно организовать форум. И он будет "просто работать". Инсталляция отнимает 5 минут. Поддержки не требуется. Хостинг будет стоит наверное вдвое меньше, чем такой же форум на PHP. Уязвимостей нет. Скрипт-киды не прорвутся. (Кстати, знаете как умер wasm.ru?)
Поймите, чудак-человек — вам говорят о том, что если вы захотите содержать не относительно небольшой портал, а реально «проходимое» место, потребуется более мощная БД. А подключить ее — ой, так просто не получится. В то время, как те же пхпшники просто подключат другую базу, фреймворк воспользуется другими заглушками для работы с ней — и все. Им не придется переписывать все. Да это может и вовсе не SQL база данных быть — все равно.
А почему такая железая уверенность в отсутствии уязвимостей?
Потому что проще код, меньше уязвимостей. У ассемблерного кода намного меньше слоев и зависимостей. Поэтому и уязвимостей меньше.
Конечно нет программы без бага. Но репорты свободно принимаются. Если найдете уязвимость или баг, то сможете увидеть как быстро она исчезнет. Потому что код на ассемблере поддерживается просто и быстро. :P
Я понял, что post здесь не связан с HTTP-методом POST. В терминологии MVC, которая используется в PHP-фреймворках, post.asm это контроллер обработки запроса на создание сущности "Post". Бизнес-логика — это поведение сущности (в данном случае при ее создании). У вас она перемешана с редиректами и sql-запросами. Например, я хочу создавать post автоматически при некотором событии, как это сделать? Если я просто вызову PostUserMessage, то в случае ошибки он прервет текущий бизнес-процесс и сделает redirect на другую веб-страницу. А если я MySQL хочу использовать, вместо SQLite?
А, кстати, даже факт обсуждения моего кода в этом ключе, показывает что я прав и идея написания веб программ на ассемблере вполне имеет право на жизнь.
Ведь хорошо я написал код или нет, это дело десятое. Примеры плохого кода на ЯВУ тоже хватает. Да и я не претендую на гениального программиста.
Тут важно понять, что ассемблер дает не меньше возможностей, а больше. Хотите например писать на ООП — пожалуйста. Хотите другую парадигму — делайте, язык вам не помешает.
При том, хорош мой код или не очень, а вы его очевидно поняли, раз можете его критиковать и приводить цитаты что там не понравилось и почему. Значит, читаемость не так уж и плохая как принято считать.
А то что надо вручную делать деталей, которые ЯВУ делают за вас, это ведь то, что делает ассемблер лучше чем ЯВУ. Кстати, это тоже вопрос выбора. Можно и так и иначе. Современные ассемблеры очень гибкие системы программирования. Это я выбрал писать деталей вручную и концентрироваться на деталях, потому что это улучшает эффективность кода.
Кстати есть и такой метод программирования, посредством естественного интеллекта — там и ЯВУ знать не нужно. Сформулируется задание человеческим языком и подается, вместе с некоторым количеством денежных знаков естественному интеллекту. После некоторого срока, на выходе получается работающая программа.
Тут важно понять, что ассемблер дает не меньше возможностей, а больше. Хотите например писать на ООП — пожалуйста. Хотите другую парадигму — делайте, язык вам не помешает…
А то что надо вручную делать деталей, которые ЯВУ делают за вас, это ведь то, что делает ассемблер лучше чем ЯВУ. Кстати, это тоже вопрос выбора. Можно и так и иначе
А еще лучше — писать просто вставками на ассемблере там, где это реально надо.
mov ebx, [ebp + 8]
mov eax, [ebx + 32]
…
mov ebx, [ebp + 8]
т.е. он чуть плоховато чувствовал, когда регистр не становился «грязным».
И код будет быстро работать на одной микроархитектуре, но запросто может оказаться хуже сишного без оптимизаций на другой.
Понятно, что у каждого проекта своя специфика, но в современном мире все стараются куда-то бежать как можно быстрее, и если останавливаться ради оптимизации, конкуренты затопчут.
Кроме того, неоптимизированный код подстегивает продажи нового оборудования. Win-win situation! Шутка. :)
Обгоняем компилятор