Почему Closure Compiler настаивает на добавлении дополнительных байтов?

Если я даю Closure Compiler что-то вроде этого:

window.array = '0123456789'.split('');

Он «компилирует» его в это:

window.array="0,1,2,3,4,5,6,7,8,9".split(",");

Теперь, как вы можете сказать, это больше. Есть ли причина, по которой компилятор Closure делает это?


person qwertymk    schedule 18.04.2012    source источник
comment
Нет, насколько мне известно, причин нет, но нет ничего идеального в минификации...   -  person Ry-♦    schedule 18.04.2012
comment
Может быть, потому что это быстрее (по крайней мере, в Chrome, пусть и ненамного): jsperf. com/empty-split-vs-comma-split Было бы интересно увидеть разницу в других браузерах... изменить: Однако в Safari и Firefox он работает медленнее... тогда я не знаю. Но в целом CC в крайних случаях предпочитает скорость, а не размер.   -  person Felix Kling    schedule 18.04.2012
comment
Может для совместимости?   -  person SLaks    schedule 18.04.2012
comment
@Matt: Вот почему я сказал может быть. Но это то, что я мог бы ожидать от IE6.   -  person SLaks    schedule 18.04.2012
comment
Моя первоначальная мысль была такой же, как у @SLaks, но я почти уверен, что это будет работать везде. Я протестировал только его в IE6, и у него нет проблем, которые я могу найти.   -  person James Allardice    schedule 18.04.2012
comment
Только что проверил - не работает в моем IE4.   -  person Florian Margaine    schedule 20.11.2012


Ответы (3)


Я думаю, что происходит, но я ни в коем случае не уверен...

Код, вызывающий вставку запятых, tryMinimizeStringArrayLiteral в PeeholeSubstituteAlternateSyntax.java.

Этот метод содержит список символов, которые, вероятно, имеют низкое кодирование Хаффмана и поэтому предпочтительны. разделить на чем другие символы. Вы можете увидеть результат этого, если попробуете что-то вроде этого:

"a b c d e f g".split(" "); //Uncompiled, split on spaces
"a,b,c,d,e,f,g".split(","); //Compiled, split on commas (same size)

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

// These delimiters are chars that appears a lot in the program therefore
// probably have a small Huffman encoding.
NEXT_DELIMITER: for (char delimiter : new char[]{',', ' ', ';', '{', '}'}) {
  for (String cur : strings) {
    if (cur.indexOf(delimiter) != -1) {
      continue NEXT_DELIMITER;
    }
  }
  String template = Joiner.on(delimiter).join(strings);
  //...
}

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

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


Еще один пример того, как компилятор работает с методом split:

"a,;b;c;d;e;f;g".split(";"); //Uncompiled, split on semi-colons
"a, b c d e f g".split(" "); //Compiled, split on spaces

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


Обновить

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

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

И поскольку алгоритм Хаффмана является значительной частью алгоритма DEFLATE, используемого gzip. Поэтому, если ваш веб-сервер настроен на использование gzip, ваши пользователи получат выгоду от этой умной оптимизации.

person James Allardice    schedule 18.04.2012
comment
@Bergi - я склонен согласиться. Тем не менее, я думаю, нам нужно увидеть больше тестов производительности в разных браузерах, сравнивающих разбиение на пустую строку и символы в массиве символов с низкой кодировкой Хаффмана, прежде чем мы сможем сказать наверняка. - person James Allardice; 18.04.2012
comment
Говоря об этом, разделение по сравнению с обычным назначением уже очень медленное: jsperf. - person Oleg V. Volkov; 18.04.2012
comment
@OlegV.Volkov - Да, и компилятор Closure преобразует вызов split в прямое присваивание, если это приводит к более короткому коду. - person James Allardice; 18.04.2012
comment
@Bergi: кажется, это серьезная ошибка, поскольку каждый "a b c d e f g".split(" "); может быть "abcdefg".split(""); и экономить почти половину байтов. - person qwertymk; 19.04.2012
comment
@FelixKling - Спасибо! Можете ли вы (или кто-либо другой) придумать любую ситуацию, которая могла бы привести к сбою или неожиданной работе .split("")? Я не думаю, что есть такие ситуации (я проверял это во многих браузерах и не нашел вообще никаких проблем), так что, вероятно, стоит написать отчет об ошибке. Это просто кажется довольно большим упущением, поэтому я удивлен, что это не всплыло раньше, что заставляет меня думать, что это может быть задумано. - person James Allardice; 19.04.2012
comment
Хорошо, это возникало раньше (я удивлен, что не нашел эту проблему раньше) - см. ссылку, размещенную thg435 в комментариях к их ответу. Не стоит подавать отчет об ошибке. - person James Allardice; 19.04.2012
comment
@James Allardice Вы слишком много доверяете компилятору. Он преобразует константу split в массив в надежде, что он сможет преобразовать массив во что-то более простое. Это было сделано для поддержки удаления неиспользуемых частей jQuery в расширенном режиме. jQuery иногда использует этот шаблон для определения методов. qwertymk, если это важно для вас, это будет довольно простой патч. - person John; 19.04.2012

Эта проблема была исправлена ​​20 апреля 2012 г. См. редакцию: https://code.google.com/p/closure-compiler/source/detail?r=1267364f742588a835d78808d0eef8c9f8ba8161

person John    schedule 24.04.2012
comment
Вероятно, вам следует проверить этот URL. - person Denys Séguret; 26.06.2013
comment
обновленный URL-адрес и добавлена ​​дата редакции, поскольку идентификаторы редакций GIT бесполезны для заказа. - person John; 27.06.2013

По иронии судьбы, split в скомпилированном коде не имеет ничего общего с split в исходном коде. Рассмотреть возможность:

Source  : a = ["0","1","2","3","4","5"]
Compiled: a="0,1,2,3,4,5".split(",")

Здесь split - это просто способ представления длинных массивов (достаточно длинных, чтобы сумма всех кавычек + запятых была длиннее split(","") ). Итак, что происходит в вашем примере? Во-первых, компилятор видит строковую функцию, примененную к константе, и сразу ее вычисляет:

'0123456789'.split('') => ["0","1","2","3","4","5","6","7","8","9"]

В какой-то момент при формировании вывода компилятор считает этот массив «длинным» и записывает его в приведенной выше «разделенной» форме:

["0","1","2","3","4","5","6","7","8","9"] => "0,1,2,3,4,5,6,7,8,9".split(",")

Обратите внимание, что вся информация о split('') в источнике к этому моменту уже потеряна.

Если бы исходная строка была короче, она была бы сгенерирована в виде массива массива без дополнительного разбиения:

Source  : a = '0123'.split('')
Compiled: a=["0","1","2","3"]
person georg    schedule 18.04.2012
comment
Так разве ['0', '1', '2', '3', '4', '5', '6', '7'] не всегда должен компилироваться в '01234567'.split('') ? - person qwertymk; 19.04.2012
comment
@qwertymk - Это именно то, что он делает, не так ли? Он скомпилирует любое прямое назначение строкового массива в вызов split, если количество требуемых символов меньше. Посмотрите на реализацию метода, упомянутого в моем ответе, чтобы точно увидеть, что происходит. - person James Allardice; 19.04.2012
comment
@JamesAllardice: я имею в виду без пробелов и пустого разделителя. - person qwertymk; 19.04.2012
comment
@qwertymk - О да, извините, я неправильно прочитал ваш комментарий. Да, наверное, это то, во что он должен скомпилироваться. Я думаю, вы, возможно, столкнулись с довольно большим упущением со стороны команды компилятора Closure! - person James Allardice; 19.04.2012
comment
@qwertymk: да, они могли бы проверить, все ли строки в массиве имеют длину 1, и предоставить для этого оптимизацию, однако, по словам одного из сопровождающих, пример с одной буквой... кажется особым случаем, и мне придется задаться вопросом, стоит ли это усилий для решения - person georg; 19.04.2012
comment
@ thg435 - Хорошая находка. Я смотрел на проблемы, но никогда не замечал этого. - person James Allardice; 19.04.2012