Более быстрый метод фильтрации

  • Автор темы wishhost
  • 48
  • Обновлено
  • 12, May 2024
  • #1
Меня не интересует ваше мнение о том, следует ли использовать для этого JavaScript, не тратьте зря время. Просто хочу улучшить производительность.

Привет, ребята, делаете что-то типа страничного фильтра и пытаетесь получить максимально быструю функцию, может ли кто-нибудь ускорить это быстрее?

https://jsbin.com/wotisuyalu/1/edit?html,js,console

Как это должно работать:

Данные структурированы следующим образом:
 var articles = {

1: {id: 1, title: 'poop', category: 2},

23: {id: 23, title: 'poop 2', category: 4},
}

Код (разметка): вывод должен представлять собой новый объект, структурированный таким же образом.

Больше беспокоит время выполнения, чем вес страницы javascript. Не стесняйтесь использовать любую библиотеку разумного размера. В настоящее время я использую lodash.

wishhost


Рег
04 Oct, 2014

Тем
1

Постов
2

Баллов
12
  • 18, May 2024
  • #2
О, если бы вы работали с исходным массивом вместо преобразованного объекта, вы могли бы ускорить его еще БОЛЬШЕ!
 

Original Rewrite Improvement New2 Improvement

Celeron J1900, 8 gigs RAM

FF Quantum 61 585 9.59x 892 14.62x

Vivaldi (chromium) 118 1047 8.87x 1476 12.51x

Edge 46 523 11.36x 683 14.85x

IE11 48 476 9.91x 927 19.31x (WTF?!?)

i7 4770k, 24 gigs RAM

FF Quantum 148 1145 7.73x 1963 13.26x

Vivaldi (chromium) 310 2322 7.49x 3249 10.48x

Edge 121 990 8.18x 1414 11.68x

IE11 123 1149 9.31x 1804 14.66x

Код (разметка): я добавил его в каталог как «new2». Обновленная диаграмма стенда:
  function filterArticles(search, status) { search = search.toLowerCase(); var statusCallbacks = [ function(article) { return search.length ? article.title.toLowerCase().indexOf(search) > -1 : true; }, function(article) { return ( search.length ? article.title.toLowerCase().indexOf(search) > -1 : true ) && article.published; }, function(article) { return ( search.length ? article.title.toLowerCase().indexOf(search) > -1 : true ) && !article.published; } ], useCallback = statusCallbacks[status || 0] || statusCallbacks[0], results = {}; for (var i = 0, article; article = articlesArray[i]; i++) if (useCallback(article)) results[article.id] = article; return results; } 
Код (разметка): индексирование массивов происходит быстрее, чем индексирование объектов, особенно если вы используете метод цикла «оценить как присваивание». (лучше всего использовать с массивами объектов или другими коллекциями, где ни один индекс массива никогда не теряется, правда!). Фильтр по-прежнему выдаст вам объект с сопоставлением клавиш в результате, даже работая с исходным массивом... Я предполагаю, что у вас была причина для этого. Так что, возможно, использовать этот необработанный массив в качестве отправной точки — не такая уж плохая идея.
 

Max_Vendetta


Рег
26 Mar, 2013

Тем
0

Постов
2

Баллов
2
  • 20, May 2024
  • #3
Во-первых, нет ничего плохого в том, чтобы сделать это как УЛУЧШЕНИЕ существующей доставки на стороне сервера или если это серверный JS. ИЛИ, если это внутренний сценарий апплета, не предназначенный для публичного просмотра.

Это плохая идея только на веб-сайтах, и даже тогда ЕСТЬ исключения (например, игры только со сценарием). Эта клиентская нагрузка БУДЕТ отстойной, если ее разместить на обычном веб-сайте, поэтому я бы не стал этого делать там, но ЕСТЬ места, где этот подход действителен.

Даже я не такой уж и придурок в этом вопросе. Похоже, у вас есть какая-то библиотека, воспроизводящая СУЩЕСТВУЮЩУЮ ФУНКЦИОНАЛЬНОСТЬ.

ох, это чертовски круто.

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

Например, _.lowerCase, который, как я полагаю, является просто оболочкой для String.toLowerCase? То же самое и с _.filter.

Я предполагаю, что это массивы, почему бы не использовать NATIVE Array.filter? (это не значит, что если вам НУЖНА устаревшая поддержка, вы не можете напрямую заполнить массив) - это часть того, почему я не люблю lodash.

Он копирует дерьмо, которое не нужно было копировать! У вас получился отличный массив, используйте обычный повседневный Array.filter!!! То, что они безумно называют обратный вызов «предикатом», — это просто еще один тупой язык, из-за которого я всегда жалуюсь.

Далее, в БОЛЬШИНСТВЕ браузеров (квантово, это стирка) использование анонимных функций медленнее, чем статических функций, поэтому, если вам не нужна привязка области, вы можете добиться большей скорости, не используя анонимные функции в качестве обратных вызовов.

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

Например, «запрос», где вы никогда не используете его после переопределения регистра, так почему бы вместо этого просто не ввести его напрямую (toLowerCase)? Кроме того, пропуск точек с запятой заставляет синтаксический анализатор работать усерднее.

и, как ни странно, сохранение длины в собственной переменной на самом деле НЕ дает вам повышения производительности.

(когда/где это дает вам импульс, это часто противоречит здравому смыслу, подъем — это странно) Да, и ваш тестовый цикл зацикливает только ваше ПЕРВОЕ действие, вы, вероятно, хотели зациклить ВСЕ из них.



Кроме того, время, необходимое для запуска теста, настолько мало, что оно ниже «коэффициента дрожания» для детализации таймера Performance.now(). Вот почему я предпочитаю выполнять цикл до тех пор, пока таймер не перевернется, а затем выполнить цикл, чтобы увидеть, сколько я могу выполнить за определенный период времени.

Итак, что-то вроде:

 

Original Rewrite Improvement
Celeron J1900, 8 gigs RAM
FF Quantum 61 585 9.59x
Vivaldi (chromium) 118 1047 8.87x
Edge 46 523 11.36x
IE11 48 476 9.91x

i7 4770k, 24 gigs RAM
FF Quantum 148 1145 7.73x
Vivaldi (chromium) 310 2322 7.49x
Edge 121 990 8.18x
IE11 123 1149 9.31x
Код (разметка): Попытка использовать фиксированное одиночное прохождение или даже количество прогонов, а затем учитывать затраченное время, может быть весьма ошибочной.

У вас НАМНОГО больше шансов получить правильные результаты, ожидая обновления таймера, а затем работая в течение фиксированного периода времени.

Также позволяет настраивать большие тесты, не беспокоясь о тайм-ауте браузера.

(Ошибка №1, которую я видел в тестах JS!) Дайте-ка посмотреть.

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

Хм. Вы знаете, во всяком случае, клавиши карты, вероятно, ДЕЙСТВИТЕЛЬНО занимают здесь больше всего времени! Возникнет соблазн как-нибудь отложить это отдельно. Ага.

ок, что ДЕЙСТВИТЕЛЬНО является вашим узким местом, так это постоянное переключение между массивом и объектом, массивом и объектом с использованием лоадэша "о, мы будем обращаться со всеми объектами как с массивами настолько неэффективно, насколько это возможно".

ПЕРВОЕ, что я бы посоветовал: если вы хотите, чтобы объект индексировался по идентификатору, СОХРАНИТЕ ИХ КАК ИДЕНТИФИКАТОРЫ!!!

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

Итак, сначала мы сделаем небольшую процедуру для преобразования вашего массива в объект:
  function filterArticles(search, status) { search = search.toLowerCase(); var statusCallbacks = [ function(article) { return search.length ? article.title.toLowerCase().indexOf(search) > -1 : true; }, function(article) { return ( search.length ? article.title.toLowerCase().indexOf(search) > -1 : true ) && article.published; }, function(article) { return ( search.length ? article.title.toLowerCase().indexOf(search) > -1 : true ) && !article.published; } ], useCallback = statusCallbacks[statusCallbacks[status] ? status : 0], results = {}; for (var i in articles) if (useCallback(articles[i])) results[i] = articles[i]; return results; } 
Код (разметка): Тогда давайте перепишем вашу процедуру, чтобы ВСЕГДА работать с ней как с объектом, вместо тупого преобразования Loadass в массив обратно в объект в мусорный массив.
  var articles = {}; for (var i = 0, e; e = articlesArray[i]; i++) articles[e.id] = e; 
Код (разметка): Да, вот и все.

Просто работайте над новым объектом непосредственно во время создания.

Ваш оригинал дал мне в среднем 320 проходов, этот перезапись — 2210, то есть фактически в семь раз быстрее.

Возможно, стоит рассмотреть и другие варианты оптимизации, например, полный отказ от использования обратных вызовов — новейшую версию Spidermonkey от Quantum, вероятно, это не волнует, но это может оказать влияние на Chakra и V8. Черт его знает, что сделают эти дерьмо, такие как Трайдент и Нитро.

Несколько быстрых тестов, в среднем более десяти тестов с 5-секундным временем восстановления, чем больше, тем лучше.

  var start = performance.now(), iterations = 0; do { stop = performance.now(); } while (start == stop); stop += 1000; // time to run do { filterArticles('a', 1); filterArticles('b', 2); filterArticles('th', 2); filterArticles('dog', 1); filterArticles('a'); filterArticles('b'); filterArticles('th'); filterArticles('dog'); iterations++; } while (performance.now() < stop); console.log(iterations) 
Код (разметка): цифры IE11 меня удивили, но движок JavaScript в IE не является настоящим узким местом — причина, по которой JavaScript IE всегда работал так медленно, заключается в том, насколько болезненно неэффективны DOM, синтаксический анализатор и средство рендеринга.

(ОСОБЕННО проклятый парсер, почему InnerHTML был таким мусором) особенно учитывая, что даже в процессе рендеринга НЕТ многопоточности.

Поскольку это даже не касается DOM или живого документа, мы можем видеть производительность JS без какого-либо вмешательства. Дальнейшие улучшения, такие как отказ от использования обратных вызовов путем развертывания цикла, МОГУТ обеспечить ускорение в некоторых движках.

Нет, я только что попробовал, это не дает реального измеримого улучшения, в пределах ожидаемого «дрожания». Хм... объект? Нет, производительность снижается до уровня, используемого loadass.

ну, не так уж плохо, все равно примерно в два раза быстрее, чем ваш оригинал, но зачем соглашаться на 2x, если вы можете достичь 11x?
ЭТОТ Вот почему я говорю, что многие фреймворки JavaScript не заставляют вас «правильно думать» о решении проблем.



Они даже не УЧИТЫВАЮТ влияние на производительность постоянного переключения между массивом и объектом! «Ошибочная методология» простого использования lodash для этого СНОВА показывает то, что я всегда говорю об этих фреймворках — это медленнее, сложнее и БОЛЬШЕ кода, чем если бы вы делали это без фреймворка! Видите, как переписанная функция занимает всего 741 байт, тогда как оригинал составлял 1052 байта? Когда я говорю «меньше, быстрее, проще и легче БЕЗ тупых фреймворков», я обычно знаю, о чем я говорю. Я бросил здесь живые примеры:
http://www.cutcodedown.com/for_others/KewL/filter/

Надеюсь это поможет.
 

DominuS


Рег
23 Dec, 2013

Тем
0

Постов
2

Баллов
2
  • 12, Jun 2024
  • #4
Есть еще более быстрый метод, который занимает часть кода, но требует небольшого изменения мышления. Я продолжаю играть с этим время от времени, несмотря на бессонницу. Клитус, мне скучно... Какую игрушку ты можешь мне предложить сегодня?
 

var

start = performance.now(),

iterations = 0;

do { stop = performance.now(); } while (start == stop);

stop += 1000; // time to run

do {

filterArticles('a', true);

filterArticles('b', false);

filterArticles('th', false);

filterArticles('dog', true);

filterArticles('a');

filterArticles('b');

filterArticles('th');

filterArticles('dog');

iterations++;

} while (performance.now() < stop);

document.body.appendChild(document.createTextNode(iterations));

console.log(filterArticles('dog'));

console.log(filterArticles('dog', true));

Код (разметка): он принимает значение true, false, null или неопределенное (то есть необязательное) в качестве «опубликованного» параметра.

Его переименовали, чтобы было понятнее, что именно он проверяет.

456 байт кода работает примерно на два-пять процентов быстрее, чем тот, который использует «обратные вызовы». использование === и typeof часто может обеспечить более быстрый и более.

последовательный поиск истинного, ложного или того и другого.

Просто используйте значение null/undefined в качестве любого из них.

Также полезно использовать сокращенную оценку, особенно потому, что JS очень строг в отношении того, как это работает.

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

Проверка буквального логического значения против нуля также выполняется быстрее, чем используемый вами числовой «статус». В каталоге это «new4». Естественно, тестовые примеры пришлось изменить следующим образом:
  function filterArticles(search, published) { search = search.toLowerCase(); if ('undefined' == typeof published) published = null; for (var i = 0, results = {}, article; article = articlesArray[i]; i++) if ( ( (search.length && (article.title.toLowerCase().indexOf(search) > -1)) || !search.length ) && ( null == published || article.published === published ) ) results[article.id] = article; return results; } 
Код (разметка):
 

ZeroCool1


Рег
12 Sep, 2013

Тем
3

Постов
7

Баллов
37
Тем
49554
Комментарии
57426
Опыт
552966

Интересно