В детстве мне часто говорили, что сахар — белая смерть. Позже я понял, что калории есть калории, а разглагольствующие о вреде сахара зачастую просто не владеют матчастью.
И вдруг выяснилось, что всё, чем меня пугали взрослые — чистая правда. Сахар жуткая вещь, которая убивает мозг и медленно ведёт нас к альцгеймеру. Его нельзя есть никому и никогда. Эта тема подробно раскрыта в книге Гэрри Тауба Good Calories Bad Calories, а также в книге Дэвида Перлмуттера Grain Brain.
Но речь в статье не об этом. Недавно я обнаружил, что группа моих товарищей плотно сидит на CoffeeScript. Они ничуть не стесняются этого факта и даже умудряются испытывать в процессе какое-то противоестественное удовольствие. Более того, они не чураются использовать содержащийся в кофе синтаксический сахар и даже хотели пристрастить к этому меня.
К счастью, я наделён редким даром. Я всегда узнаю зло, и неважно в какие одежды оно вырядилось на этот раз.
Первую дозу нам заботливо (и абсолютно бесплатно) предлагают на официальном сайте. Там, прямо на стартовой странице, выложен код на кофескрипте и джаваскриптовый код, в который его превратит компайлер.
Надо отдать людям должное — они не пытаются вас обмануть. Приведённого на стартовой странице кода более чем достаточно, чтобы навсегда отказаться от синтаксического сахара и вообще десять раз подумать перед тем, как начать употреблять заморский напиток. Я не буду приводить код целиком — тем более, что каждый может поглядеть на него самостоятельно. Приведу лишь значимый кусок.
list это массив, а math.cube, как нетрудно догадаться, функция, возводящая входной параметр в куб.
Никто конечно в здравом уме и светлой памяти такого кода сам бы не написал. За этим многобуквием скрывается простая и известная конструкция.
cubes = list.map(math.cube);
Что, кстати понятнее и короче, чем кофемановый вариант.
Да, с эстетической точки зрения джаваскриптовый код просто ужасен, но когда я намекнул на это товарищам, мне обоснованно возразили, что это выхлоп компилятора, который нормальные люди не читают. Напротив, надо посмотреть на этот код и порадоваться, что тебе никогда не придётся писать его самому.
Точка зрения не новая, многократно подтверждённая на практике такими монстрами как, например, Бьёрн Страуструп. Если компилятор не порет косяков, то действительно лучше довериться ему, расслабиться и получать удовольствие.
Однако одного взгляда на приведённый выше код достаточно для того, чтобы осознать — при виде сахара компилятор теряет разум. В первую очередь в глаза бросается постинкреметный оператор, но он, конечно, погоды не делает.
Настоящая проблема в том, что код, сгенерированный компилятором, примерно в 3 раза медленнее, чем cubes = list.map(math.cube);. Да, совершенно верно, основательно закинувшись сахаром, можно совершенно бесплатно троекратно замедлить часть кода не получив взамен ничего.
Причина проста и прозаична до безобразия. То, что в javascript называется массивами, в других языках программирования называется хэшами, а время доступа по ключу к элементу хэша это не тоже самое, что время доступа к элементу массива по индексу. С другой стороны, элементы хэша связаны в список, поэтому последовательный перебор всех элементов можно сделать быстро и просто. Не так быстро и не так просто, как элементы настоящего массива, но в разы быстрее, чем это делает код, сгенерированный компилятором CoffeeScript.
Как некоторые наверное уже заметили — cubes = list.map(math.cube); делает то же самое, что код на кофескрипте, но на самом деле не является его прямым отображением. Точно отобразить этот код можно так:
Может быть дело в том, что в у нас каждый раз возвращается новый массив? Может быть всё дело в .push и в том, что массив приходится выращивать? Ну то есть понятно, что это невозможно, но вдруг? Может надо так?
Это вообще отображение один в один, всё за исключением перебора взято из оригинала. Но даже такой код всё равно в 3 раза быстрее, чем код с заглавной страницы CoffeeScript.
А может быть дело не только в доступе к элементам? Давайте попробуем просто суммировать значения элементов массива, а не перекладывать их в новый массив.
Сравним этот код:
Вот с этим:
Результаты уже не так однозначны. Но разница всё равно в полтора раза в пользу джаваскрипта.
С моей точки зрения, если технология прямо с порогапробивает с ноги делает такие заявки, это повод серьёзно подумать перед тем, как начинать её использовать. Конечно, если вы балуетесь CoffeeScript уже давно, и он вас радует — продолжайте баловаться — кофе сам по себе ещё никого не убил. Особенно учитывая, что лямбды безо всяких сложностей можно использовать в CoffeeScript и компилятор транслирует их именно в лямбды, у которых, как мы сейчас выяснили, проблем со скоростью нет.
Но вот с сахаром, по крайней мере с некоторыми его видами, лучше завязать — sugar is bad for ya.
Посмотреть код тестов можно вот тут.
Сведения изложенные в статье справедливы для node версии v0.10.40. В последней версии node и в последних версиях браузеров картина другая. В Хроме версии 45 код, сгенерированный CoffeeScript медленней, в файфоксе вроде быстрее. Хотя надо собрать побольше статистики.
И вдруг выяснилось, что всё, чем меня пугали взрослые — чистая правда. Сахар жуткая вещь, которая убивает мозг и медленно ведёт нас к альцгеймеру. Его нельзя есть никому и никогда. Эта тема подробно раскрыта в книге Гэрри Тауба Good Calories Bad Calories, а также в книге Дэвида Перлмуттера Grain Brain.
Но речь в статье не об этом. Недавно я обнаружил, что группа моих товарищей плотно сидит на CoffeeScript. Они ничуть не стесняются этого факта и даже умудряются испытывать в процессе какое-то противоестественное удовольствие. Более того, они не чураются использовать содержащийся в кофе синтаксический сахар и даже хотели пристрастить к этому меня.
К счастью, я наделён редким даром. Я всегда узнаю зло, и неважно в какие одежды оно вырядилось на этот раз.
Первую дозу нам заботливо (и абсолютно бесплатно) предлагают на официальном сайте. Там, прямо на стартовой странице, выложен код на кофескрипте и джаваскриптовый код, в который его превратит компайлер.
Надо отдать людям должное — они не пытаются вас обмануть. Приведённого на стартовой странице кода более чем достаточно, чтобы навсегда отказаться от синтаксического сахара и вообще десять раз подумать перед тем, как начать употреблять заморский напиток. Я не буду приводить код целиком — тем более, что каждый может поглядеть на него самостоятельно. Приведу лишь значимый кусок.
CoffeeScript |
JavaScript |
|
|
list это массив, а math.cube, как нетрудно догадаться, функция, возводящая входной параметр в куб.
Джаваскриптовый код кошмарен
Никто конечно в здравом уме и светлой памяти такого кода сам бы не написал. За этим многобуквием скрывается простая и известная конструкция.
cubes = list.map(math.cube);
Что, кстати понятнее и короче, чем кофемановый вариант.
А в чём собственно проблема
Да, с эстетической точки зрения джаваскриптовый код просто ужасен, но когда я намекнул на это товарищам, мне обоснованно возразили, что это выхлоп компилятора, который нормальные люди не читают. Напротив, надо посмотреть на этот код и порадоваться, что тебе никогда не придётся писать его самому.
Точка зрения не новая, многократно подтверждённая на практике такими монстрами как, например, Бьёрн Страуструп. Если компилятор не порет косяков, то действительно лучше довериться ему, расслабиться и получать удовольствие.
Однако одного взгляда на приведённый выше код достаточно для того, чтобы осознать — при виде сахара компилятор теряет разум. В первую очередь в глаза бросается постинкреметный оператор, но он, конечно, погоды не делает.
Настоящая проблема в том, что код, сгенерированный компилятором, примерно в 3 раза медленнее, чем cubes = list.map(math.cube);. Да, совершенно верно, основательно закинувшись сахаром, можно совершенно бесплатно троекратно замедлить часть кода не получив взамен ничего.
Как так вышло
Причина проста и прозаична до безобразия. То, что в javascript называется массивами, в других языках программирования называется хэшами, а время доступа по ключу к элементу хэша это не тоже самое, что время доступа к элементу массива по индексу. С другой стороны, элементы хэша связаны в список, поэтому последовательный перебор всех элементов можно сделать быстро и просто. Не так быстро и не так просто, как элементы настоящего массива, но в разы быстрее, чем это делает код, сгенерированный компилятором CoffeeScript.
Может мы на самом деле измеряем разные вещи
Как некоторые наверное уже заметили — cubes = list.map(math.cube); делает то же самое, что код на кофескрипте, но на самом деле не является его прямым отображением. Точно отобразить этот код можно так:
cubes = list.reduce(function(prev, curr) {
return prev.concat(math.cube(curr));
}, []);
Может быть дело в том, что в у нас каждый раз возвращается новый массив? Может быть всё дело в .push и в том, что массив приходится выращивать? Ну то есть понятно, что это невозможно, но вдруг? Может надо так?
list.reduce(function(result, curr) {
result.push(math.cube(curr));
return result;
}, []);
Это вообще отображение один в один, всё за исключением перебора взято из оригинала. Но даже такой код всё равно в 3 раза быстрее, чем код с заглавной страницы CoffeeScript.
Но можно быть ещё правдивей
А может быть дело не только в доступе к элементам? Давайте попробуем просто суммировать значения элементов массива, а не перекладывать их в новый массив.
Сравним этот код:
var sum = (function(list) {
var i, len, sum = 0;
for (i = 0, len = list.length; i < len; i++) {
var num = list[i];
sum += num;
}
return sum;
}(list));
Вот с этим:
var sum = list.reduce(function(prev, curr) {
return curr + prev;
}, 0);
Результаты уже не так однозначны. Но разница всё равно в полтора раза в пользу джаваскрипта.
Такие дела
С моей точки зрения, если технология прямо с порога
Но вот с сахаром, по крайней мере с некоторыми его видами, лучше завязать — sugar is bad for ya.
P.S.
Посмотреть код тестов можно вот тут.
Update
Сведения изложенные в статье справедливы для node версии v0.10.40. В последней версии node и в последних версиях браузеров картина другая. В Хроме версии 45 код, сгенерированный CoffeeScript медленней, в файфоксе вроде быстрее. Хотя надо собрать побольше статистики.
lega
Зачем вы пишите
когда вы можете написать И получите тот же самый код что и в js. CoffeуScript просто дает вам варианты.По идее вызов функции на каждую итерацию должен быть медленнее простого суммирования, возможно V8 научился хитро оптимизировать, но нужно будет перепроверить.
PS: Вам надо было код вложить на jsperf, что-б любой желающий мог запустить, так же увидели бы результаты в разных браузерах.
lega
Вот добавил ваш последний тест на jsperf, в Chrome CS-цикл быстрее чем JS reduce более чем в 2 раза, а в Firefox CS-цикл быстрее в 20 раз.
poxu
Прикольно. Звезда в шоке. Я гонял тесты node.js.
poxu
Так. Вот тесты для суммирования массивов. У меня в файрфоксе и хроме зеркальная картина. Хром 45-ой версии.
Далее. Нод, на котором я гонял тесты как выяснилось старый. Версии v0.10.40. В новом ноде пока не проверял сам, но друзья кофеманы говорят, что лямбды не рулят. Сейчас немного подумаю и добавлю апдейт с опровержением, или нову статью сделаю с разбором.
RubaXa
Все массивные методы написаны на JS и сильно избыточны, они априори не могут быть быстрей, почитайте/посмотрите Далтона, он в докладах про lodash хорошо разжевывает суть, ну и тестов у него ого-го.
poxu
По измерениям в немного устаревшей версии node и в текущей версии Chrome map быстрее. Ну и я верю, что в джаваскрипте рано или поздно начнут инлайнить функции.
RubaXa
Проверьте ваш тест, map/forEach/reduce и остальные избыточны, они всегда работают через `call`, но самое главное в них используется мега тормознутый `in`, выкинув его, мы уже получим 5х ускорение (но, да, таким образом мы получим иное поведение чем в native реализации, но вот нужно ли такое поведение? Скорее да, чем нет, ну и тесты говорят за себя).
— Браузерные тесты: jsperf.com/arraymap/14 (как и ожидалось самый медленный нативный)
— Вот ещё я накидал простенький тест, чтобы показать, где и как можно выиграть.
poxu
Могу и даже рекомендовал писать именно его в заключении. Но приведённый мной код — цитата с главной страницы сайта CoffeeScript. Поэтому он и был подвергнут разбору.
За совет с jsperf спасибо.
SerafimArts
Открою секрет, что выхлоп кофе один в один такой, как написал бы человек. Если человек написал бы циклом for, то и выхлоп соответствующий, как если бы он написал циклом for, если бы воспользовался методом forEach или map, то соответственно.
SerafimArts
С другой стороны не всё так идеально, как хотелось бы. Вот пример на кофе:
А вот результат:
Как недавно выяснил — в этом варианте есть небольшие проблемы: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
poxu
Если бы array comprehensions были частью javascript, замечание было бы справедливым. Но там их нет и поэтому for из coffeescript имеет с for циклом столько же общего, сколько с ним имеет for in.
SerafimArts
for i in (arr[Symbol.iterator] || arr)
работает точно так же (Ну почти, там ещё next надо брать) какfor of
в JS. Ну и наоборот: http://coffeescript.org/#try:for%20i%20of%20%7Ba%3A%201%2C%20b%3A%202%7D%0A%20%20console.log(i)Что с этими циклами не так?
poxu
и
Это две разные конструкции, вот что я хочу сказать.
SerafimArts
и
Тоже две совершенно разные конструкции в кофе. И точно так же как в вашем примере, одна по массиву, другая по объекту. Я так и не понял какую мысль вы хотите донести.
poxu
Я хочу сказать, что проход по массиву в джаваскрипте делается с помощью forEach map и им подобными. Делается не только из соображений удобства, но и из соображений производительности. А coffeescript при трансляции выбирает медленный вариант.
SerafimArts
А что мешает написать на кофе:
?
А кофе вообще ничего не выбирает. Это написавший написал цикл, а не вызвал метод.
Да и цикл побыстрее будет метода. Я не понимаю почему вы ознакомившись с тестами http://habrahabr.ru/post/268753/#comment_8611909 до сих пор пытаетесь всех убедить, что это медленный вариант.
Предлагаю осилить наконец кофе и понять, что это не отдельный язык, а тот же самый JS. Как вы написали на кофе так и будет в JS, один в один, буква к букве. А на главной странице показаны лишь отличия в основных конструкциях языка, а не как что-либо делать.
nazarpc
Вы ничего не перепутали? В JS массивы как массивы, там только цифровые индексы, и:
Ожидаемо выдаст 3, просто массивы здесь динамические по размеру. Но это не PHP, где массивы — это всё что угодно, только не обычные массивы.
О
cubes = list.map math.cube
вам уже написали (хотя я предпочитаю ставить в таких случаях скобки, то есть единственное отличие будет в отсутствии запятой).А теперь попробуйте сделать такое на JS:
И тогда мы поговорим о сравнении с чистым JS.
Не стоит винить синтаксический сахар в том, что его используют неправильно.
poxu
Увы, это не так. Массивы в джаваскрипте примерно такие же как в php. Собственно если в языке можно писать и array[1] и array['ke'] то они другими и быть не могут.
Не стоит использовать его там, где в нём нет нужды. И тем более не стоит выкладывать примеры бесполезного использования на главную страницу.
jj_killer
Массивы в JS это обычные массивы, а то о чем вы пишите, хорошо объясняется тут: www.2ality.com/2011/08/array-prototype-performance.html
poxu
Когда вы говорите, что массивы в js это обычные массивы, вы имеете в виду, что они располагаются в одном непрерывном куске памяти?
jj_killer
Причем тут это? Как что располагать в памяти решает производитель рантайма исходя из огромного количества факторов. Мы тут говорим о дизайне языка, стандарт которого вообще не описывает реализацию рантайма.
poxu
При том, что доступ к элементам массива, находящегося в непрерывном куске памяти происходит практически мгновенно. В отличие от хэша.
И да, в джаваскрипте действительно не оговорено как располагать в памяти массив. Но оговорено, то индексы массивов могут быть строками и могут содержать дырки. Что естественным образом приводит к необходимости держать его в хэше. Вследствии чего доступ по индексу мендленный.
lega
При правильном использовании массивов, оптимизатор использует массивы, а не хеш таблицы.
nazarpc
Индексы не могут быть строками, не путайте. Массивы в JS — это объекты, так что array['ke'] это доступ к свойству этого объекта, не к элементу массива, так же как и более привычное array.ke.
Дырки могут содержать, это да, размер-то массива может быть больше чем количество элементов в нём, ничего странного в этом нет. А в том же PHP размер массива при удалении сразу же меняется (мы не говорим сейчас об SPL семействе).
SerafimArts
> Увы, это не так. Массивы в джаваскрипте примерно такие же как в php. Собственно если в языке можно писать и array[1] и array['ke'] то они другими и быть не могут.
Всегда считал, что в JS массивы — это объект Array (методы forEach. map, etc) с цифровыми ключами и Symbol.iterator методом, а в пыхе — это примитив в чистейшем виде, они совершенно разные как по поведению, так и по реализации (одно объект, другое примитив). Более того, если в JS квадратные скобки являются элементом доступа к полю объекта, то в пыхе за это отвечает ArrayAccess интерфейс и соответствующая реализация оного. Так же и за итерацию, в JS это Symbol.iterator, а в пыхе IteratorAggregate интерфейс (и прочие). Т.е. вы как минимум сравниваете тёплое с мягким. Просьба поправить, если я заблуждаюсь.
poxu
В php есть встроенные в язык массивы. Они — хэши. Ещё есть способ разрешить любому объекту весте себя как массив — ArrayAccess. Это как перегрузка оператора []. И есть настоящие массивы фиксированной длины с однотипными элементами.
nazarpc
Они не чистые хэши. Там весьма сложная внутренняя структура, которая включаэт и хэши, и списки, вот хорошая презентация: slides.catchy.io/PHP-Data-Structures-and-the-impact-of-PHP-7-on-them.html
И (что немного парадоксально), они хотя и потребляют больше памяти, намного быстрее в типичных задачах за те же SPL реализации. В презентации много интересного.
poxu
Не чистые хэши, но одного непрерывного куска памяти за массивом в php нет. В документации так и написано — ordered map. Это даёт возможность бысть перебирать элементы по порядку, но не даёт быстрого доступа по индексу.
Насчёт презентации — не подскажете ссылку на видео?
SerafimArts
Именно это я и написал с просьбой поправить следующее: Я высказался, что в js массивы вообще ничем не отличаются от обычного Object, включая способ доступа к элементам этого «массива», а в пыхе реализовать их невозможно по определению — передача по значению объекта невозможна на уровне реализации объекта.
vintage
Array.prototype['0'] = 'x'
console.assert( Array(2).length === 2 )
console.assert( Array(2)[0] === 'x' )
SerafimArts
del
jj_killer
Мне кажется вы немного теряете контекст, этот пример висит там уже без малого 5 лет. В то время, реально поддерживаемые браузерами возможности javascript немного отличаются от теперешних. Насколько я понимаю CoffeeScript до сих пор работает хорошо с IE8, и все это оставлено в угоду совместимости.
poxu
К счастью, IE8 в нашем мире осталось не так уж и много. Совместимость это хорошо, но когда совместимость достигается в угоду производительности это уже не так хорошо. Было бы неплохо ключом компиляции это настраивать хотя бы.
SerafimArts
Ну так map, reduce и прочие, как оказалось выше (http://habrahabr.ru/post/268753/#comment_8611909) в разы медленнее обычного цикла. Ключ для понижения производительности? А учитывая то, что выражение на кофе один в один будет как выражение на JS, то и в угоду читаемости выхлопа (а учитывая sourcemap, его вообще читать не нужно)?
jj_killer
Сделать отдельные ключи это не так просто, особенно если учесть что их нужно подвязывать на какую-то конкретную реализацию рантайма, например, node (у которого и своих ключей совместимости полно). И вообще не ясно, как все это будет влиять на реальную производительность.
Envek
Вот зря вы так. Мы тут на новом проекте еле-еле отбились от IE8. Он, к сожалению, много ещё где живее всех живых, особенно в мирах, где правит кровавый ынтырпрайз.
o_nix
Ровно
в полночьраз в год появляется очередная статья о том, насколько же кофе ужасен и насколько плохо его читать тем, кто его не осилил. Не кажется ли, что это уже не модно?poxu
Статья о том, что CoffeScript генерирует медленный код, а не о том, что его плохо читать.
nazarpc
Уважаемый, трюк в том, что при написании кода на CoffeeScript думать нужно на порядок больше, чем на JS.
Читать код проще, понимать тоже в целом легко, если знаете синтаксис, но при написании кода нужно четко понимать не только то, что вы хотите получить, но и то, как оно будет выглядеть в JS.
Вы же приводите два совершенно разных куска кода и говорите что один быстрее за другой. Он не может быть быстрее или медленнее просто по определению, вы используете совершенно разные подходы для достижения одной и той же цели!
poxu
Если при написании кода на CoffeeScript действительно надо думать на порядок больше, то на нём лучше не писать. Весь смысл использования языка, отличного от джаваскрипта в том, чтобы облегчить написание кода, а не усложнить его.
Ну и если есть 2 совершенно разных куска кода, которые используются для достижения одной и той же цели и один кусок быстрее другого, то лучше использовать инструмент, который генерит быстрый код.
SerafimArts
Одно не противоречит другому. Паскаль, к примеру, очень простой язык, но не предпочитать же его, например шарпу. Так же и тут — кофе удобнее и мощнее ES5.
poxu
Причина не предпочитать паскаль шарпу — поддержка современных платформ, сборщик мусора, concurrency, виртуальная машина со стектрейсами, богатая стандартная библиотека и коммьюнити.
CoffeScript не может сделать ничего, что не может джаваскрипт, соответственно причина выбирать его — простота написания кода и ошибкобезопасность. Я уверен, если сообщить создателям CoffeeScript, что на их языке писать сложнее, чем на джаваскрипте, они ответят, что стремились создать язык, на котором писать проще.
SerafimArts
На паскале тоже это можно реализовать. Так же как на ES5 написать классы. А то что это долго делать и результат не очень красивый будет — это не так уж и важно, правда? Почему вы защищаете шарп, это ведь почти такой же сахар по сравнению с паскалем? И, я должен это сказать, боже, он медленнее!
Я хочу донести до вас простую мысль — вы противоречите сами себе. Кофе нужен для того, что бы облегчить жизнь, так же как и любой более современный язык, по сравнению с более древним. Он сложнее для изучения, как и любой современный язык, но профита от него в разы больше при умелом использовании, код становится чище и читаемее. Но между паскалем и шарпом есть одна большая разница — это разные языки, корректнее сравнивать паскаль и дельфи.
В вашей статье приводятся примеры совершенно разных языковых конструкций и немое удивление почему они работают по разному с разной скоростью. Это как написать «float a» на одном языке и «double a» на другом, удивляясь почему второй жрёт оперативы в два раза больше, даже не осознавая то, что можно и там и там написать и «float a». И читая подобную некомпетентность (точнее откровенное незнание кофе, как языка), у меня, признаюсь, очень сильно подгорает. =)
poxu
Чтобы реализовать всё, что есть в C# на паскале — придётся написать на нём vm и компилятор. И код в основном будет работать с той же скоростью, что С#. Игра не стоит свеч.
Гораздо лучше пример с С и С++. 2 разных языка, один является подмножетсвом другого, но все оптимизации, которые можно сделать для того, чтобы производительность С++ была такой же, как С — обязательно делаются.
Современные языки сложнее старых в том смысле, что в старых многие фичи реализованы только в библиотеках и потому меньше ключевых слов. Но думать там зачастую можно меньше, потому что есть больше гарантий по поводу того, как будет вести себя код.
Сравнение double c float тут некорректно, потому что такую ситуацию нельзя выловить компилятором. А ситуацию из статьи — можно. И приходится помнить, что конструкцию не надо применять, если шаг равен еденице. Это очень неприятно.
То, что вы классифицируете как незнание языка — на самом деле претензия к компилятору, который не оптимизирует ситуацию, которую оптимизировать можно.
SerafimArts
А вы понимаете что надо сравнивать код на кофе:
С этим на JS:
И соответственно код на Coffee:
С этим кодом на JS:
Что бы уже были какие-то реальные претензии к транслятору и его оптимизациях.
Вы пишете цикл, а не вызываете метод и удивляетесь, что на выходе цикл, а не метод. Сколько раз это надо повторить всему сообществу хабра, что бы до вас наконец, простите, дошло, что вы сравниваете совершенно разные вещи?
poxu
Если бы CoffeeScript выполнялся браузером, а не транслировался в javascript, то смысл делать сравнения, о которых вы говорите, был бы.
А тут такое дело. Мы точно знаем во что компилятор транлирует cubes = (math.cube num for num in list). И знаем во что он мог бы его транслировать для улучшения производительности. И знаем, что причин не делать этой оптимизации нет. Если мне приведут причины не делать этой оптимизации, до меня дойдёт, что я сравнивал совершенно разные вещи.
Причина, которую приводите вы — нужно транслировать for в синтаксически сходную конструкцию. Я считаю, что сохранить синтаксис не важно, так как это не даст ничего — важно сохранить семантику. Она при такой оптимизации сохранится.
SerafimArts
В ваших словах есть смысл, если считать кофе отдельным языком. Но проблема в том, что кофе — это транслятор, один-в-один. Все оптимизации делает программист так же, как он делал бы это на JS. У него такая идеалогия. Во втором абзаце на главной странице так и написано: «The golden rule of CoffeeScript is: 'It is just JavaScript'.». Он не извращает язык, не оптимизирует, он просто сокращает синтаксис приводит его в такой вид, какой бы любой человек написал бы на JS. Именно в этом и причины отсутствия каких-либо сторонних манипуляций с кодом, всё транслируется слово-в-слово и можно дословно перевести js в кофе и обратно, не потеряв практически не единой строчки кода.
Если же нужны оптимизации — никто не мешает поверх навесить какой-нибудь гулповский uglifer или ещё что.
nazarpc
Знаете, а я предпочитаю больше думать и меньше писать. А вообще, код чаще читают чем пишут (несравнимо чаще), и вот читать CoffeeScript при разумном использовании существенно легче, полагаю, для этого он и создан в первую очередь (радикально меньше визуального мусора).
При чем тут генерация вообще? Если бы у вас был шаг не 1, а 2 (как в моем примере выше) — у вас бы код был аналогичной конструкции.
На CoffeeScript можно и нужно писать
cubes = list.map(math.cube)
— это короче, понятнее и (судя по тому, что вы доказывали в статье) даже быстрее.А то, что вы на JS взяли простой вариант, а на CoffeeScript взяли неудачный пример из документации (которая просто показывает что так можно делать, но не обязательно нужно) — это ваша личная проблема и ни чья больше. Повторюсь ещё раз,
cubes = list.map(math.cube)
— вилидный CoffeeScript, который не быстрее и не медленнее такой же строчки на JS. А то, что вы написали называется высасывание проблемы с пальца.poxu
История развития языков программирования это история того, как сделать так, чтобы при написании кода можно было меньше думать. Если думать надо больше, то и неверных решений будет принято больше и ошибок значит будет больше.
Если есть способ оптимизировать случай с шагом в 1 — надо это сделать.
nazarpc
Не совсем, код делает ровно то, что вы написали. Хотите пройтись циклом и вернуть массив значений — код делает ровно это и ничего больше. На JS код был бы аналогичный выхлопу компилятора CoffeeScript. Если хотите применить .map — код опять абсолютно аналогичный.
Ваши примеры НЕ эквивалентны, они совершенно разные, как вы не поймете это! Что написали — то и получили.
poxu
комментарий удалён
dom1n1k
Что-то я не совсем понял, почему map внезапно превратился в reduce?
poxu
Потому, что код, сгенерированный CoffeeScript внутри устроен как reduce. Там есть накопитель, и цикл с его наполнением. map делает массив по размеру равный исходному и потом просто присваивает элементам массива нужные значения. Поэтому логично сравнивать код, сгенерированный CoffeeScript с reduce, а не с map.
dom1n1k
Ну, во-первых, это сугубо его проблемы, как он устроен. Главное это смысловая суть кода.
А во-вторых, там нет вызова функции на каждой итерации. Так что я совершенно не уверен, что это справедливая замена.
Zibx
Как я и подозревал скорость срезалась в основном поиском функции cube в пачке замыканий вплоть до window. Вот пример с подтягиванием поближе.
Zibx
Map мог оказаться быстрее цикла только за счёт оптимизации взятия элемента массива и оптимизации складывания в новый, но forEach всегда медленнее обычного цикла из-за накладных расходов на вызов функции. Самым оптимальным решением в данном случае будет раскрыть функцию и написать x*x*x прям в теле цикла.
dom1n1k
Насчет forEach — да, по всей логике и здравому смыслу он должен быть медленнее. Но на практике — нифига! Я как-то тестировал и довольно много сравнивал — вполне сопоставимо он работает, а иногда даже существенно быстрее. У меня нет внятного объяснения этого феномена, но факт есть факт.
poxu
А вызовы функций не инлайнит никто?
Yuuri
Лучше бы она была раскрыта в научно достоверных статьях из реферируемых источников.
poxu
Она раскрыта. В книгах этих ссылок много.
Yuuri
Вас не затруднит привести несколько? Как-то не хочется только ради этого книжку покупать.
poxu
Несколько прям сразу сложно. Вот ссылка, которая оказалась под рукой www.ncbi.nlm.nih.gov/pubmed/24115747.
rock
Вы не понимаете несколько простых вещей. Да, выхлоп кофе неоптимален, но исключительно из-за использования
.push
вместо создания массива с заранее заданной длинной. В современных js движках массивы как раз таки массивы, а не хэши, если содержимое однотипно. В V8 методы массивов, вы не поверите, написаны на том же самом js и мало отличаются от представленного выше «оптимизированного» выхлопа кофе — тот же самый цикл и получение / установка элементов по индексу. Вот только с ненужной нам проверкой «дырки» в массиве — необходимо по стандарту. Да, и V8 инлайнит функции, но не коллбэки.map
и ему подобных методов (хотя может что в последнее время и изменилось).poxu
Да, действительно не понимал. Спасибо за комментарий и за ссылки.
Valery4
После этой фразы — мне стало совершенно понятно, что автор будет выступать в роли истины в последней инстанции…
seth2810
А почему никто не попробовал убрать вычисление длины массива на каждой итерации и закэшировать его например в пременной. Кажется, что в этом есть некоторая толика отличия for от map?
poxu
Да оно кешируется.
И в последних версиях нода и в файрфоксе for быстрее, чем map.
Вся ценность статьи в коментаторах, которые объясняют, что в ней не так.