Nano Hash - криптовалюты, майнинг, программирование

почему макрос времени требует очень короткого прошедшего времени для медленного вызова функции?

Просматривал упражнения в конце главы 9 clojure для смелых и верных (в частности последний из поиска нескольких движков и возврата первого совпадения каждого)

Я издевался над фактическим поиском с частью slurp:

(defn search-for
  [query engine]
  (Thread/sleep 2000)
  (format "https://www.%s.com/search?q%%3D%s", engine query))

И реализовал такое поведение:

(defn get-first-hit-from-each
  [query engines]
  (let [futs (map (fn [engine]
                    (future (search-for query engine))) engines)]
    (doall futs)
    (map deref futs)))

(Я знаю, что возвращаемым значением здесь является список, и упражнение запрашивает вектор, но для этого можно просто выполнить into...)

но когда я запускаю это в REPL

(time (get-first-hit-from-each "gray+cat" '("google" "bing")))

кажется, что это занимает 2 секунды после того, как я добавил doall (поскольку карта возвращает ленивую последовательность, я не думаю, что какое-либо будущее даже начнется, если я не использую последовательность, (last futs) также работает), но когда я использую макрос time в REPL сообщает, что время почти не расходуется, даже если это занимает 2 секунды:

(time (get-first-hit-from-each "gray+cat" '("google" "bing")))
"Elapsed time: 0.189609 msecs"
("https://www.google.com/search?q%3Dgray+cat" "https://www.bing.com/search?q%3Dgray+cat")

что здесь происходит с макросом time?


Ответы:


1

Вкратце: ленивые последовательности плохо сочетаются с макросом time, а ваша функция get-first-hit-from-each возвращает ленивую последовательность. Чтобы ленивые последовательности работали с time, оберните их в doall, как это предлагается в документации< /а>. См. Ниже более полный мыслительный процесс:

Ниже приводится определение макроса time в clojure.core (источник):

(defmacro time
  "Evaluates expr and prints the time it took.  Returns the value of
 expr."
  {:added "1.0"}
  [expr]
  `(let [start# (. System (nanoTime))
         ret# ~expr]
     (prn (str "Elapsed time: " (/ (double (- (. System (nanoTime)) start#)) 1000000.0) " msecs"))
     ret#))

Обратите внимание, как макрос сохраняет возвращаемое значение expr в ret#, сразу после чего печатает прошедшее время? Только после этого возвращается ret#. Ключевым моментом здесь является то, что ваша функция get-first-hit-from-each возвращает ленивую последовательность (поскольку map возвращает ленивую последовательность):

(type (get-first-hit-from-each "gray+cat" '("google" "bing")))
;; => clojure.lang.LazySeq

Таким образом, когда вы выполняете (time (get-first-hit-from-each "gray+cat" '("google" "bing"))), в ret# сохраняется ленивая последовательность, которая на самом деле не оценивается, пока мы не попытаемся использовать ее значение...

Мы можем проверить, была ли вычислена ленивая последовательность, используя функцию realized?. Итак, давайте настроим макрос time, добавив строку, чтобы проверить, было ли вычислено ret#, сразу после печати прошедшего времени:

(defmacro my-time
  [expr]
  `(let [start# (. System (nanoTime))
         ret# ~expr]
     (prn (str "Elapsed time: " (/ (double (- (. System (nanoTime)) start#)) 1000000.0) " msecs"))
     (prn (realized? ret#)) ;; has the lazy sequence been evaluated?
     ret#))

Теперь тестируем:

(my-time (get-first-hit-from-each "gray+cat" '("google" "bing")))
"Elapsed time: 0.223054 msecs"
false
;; => ("https://www.google.com/search?q%3Dgray+cat" "https://www.bing.com/search?q%3Dgray+cat")

Нет... так вот почему time печатается неточно. Ни одна из вычислительно-длинных вещей фактически не запускается до того, как будет сделана распечатка.

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

(defn get-first-hit-from-each
  [query engines]
  (let [futs (map (fn [engine]
                    (future (search-for query engine))) engines)]
    (doall futs)
    (doall (map deref futs))))
;; => #'propeller.core/get-first-hit-from-each

(time (get-first-hit-from-each "gray+cat" '("google" "bing")))
"Elapsed time: 2005.478689 msecs"
;; => ("https://www.google.com/search?q%3Dgray+cat" "https://www.bing.com/search?q%3Dgray+cat")

или внутри time, оборачивая вызов функции:

(time (doall (get-first-hit-from-each "gray+cat" '("google" "bing"))))
06.07.2020

2

Что-то странное происходит в вашей установке. Он работает, как и ожидалось, за 4 секунды:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn search-for
      [query engine]
      (Thread/sleep 2000)
      (format "https://www.%s.com/search?q%%3D%s", engine query))

(defn get-first-hit-from-each
      [query engines]
      (let [futs (map (fn [engine]
                        (future (search-for query engine)))
                      engines)]
        ; (doall futs)
        (mapv #(println (deref %)) futs)))

(dotest
  (time
    (get-first-hit-from-each "gray+cat" '("google" "bing"))))

с результатом

--------------------------------------
   Clojure 1.10.2-alpha1    Java 14
--------------------------------------

Testing tst.demo.core
https://www.google.com/search?q%3Dgray+cat
https://www.bing.com/search?q%3Dgray+cat
"Elapsed time: 4001.384795 msecs"

Я даже не использовал doall.


Обновлять:

Я нашел свою ошибку. Я случайно использовал mapv вместо map в строке 15. Это заставляет его ждать каждого вызова deref. Если вы используете здесь map, вы получите ленивую последовательность ленивой последовательности, и функция завершится до истечения времени таймера (дважды => 4 секунды).

--------------------------------------
   Clojure 1.10.2-alpha1    Java 14
--------------------------------------

Testing tst.demo.core
"Elapsed time: 0.182797 msecs"

Я всегда рекомендую использовать mapv вместо map. Также имеется filterv. Если вы сомневаетесь, преобразуйте выходные данные в красивый энергичный вектор, используя (vec ...), чтобы избавить себя от головной боли из-за лени.

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

P.S.

См. этот список документации, включая прекрасную CheatSheet по Clojure.

P.P.S.

ОП прав, что в идеале каждый запрос должен выполняться параллельно в отдельном потоке (каждый future использует отдельный поток). Проблема опять же в ленивом map поведении.

В конце println каждый элемент в ленивом списке из futs не оценивается до тех пор, пока не потребуется println. Таким образом, они даже не начинаются позже, по очереди. Это противоречит намеченной цели параллельного выполнения. Опять же, причиной является поведение lazy-seq.

Лекарство: делайте все явным и нетерпеливым в 99+ процентах случаев (т. е. mapv):

(defn search-for
      [query engine]
      (Thread/sleep 2000)
      (format "https://www.%s.com/search?q%%3D%s", engine query))

(defn get-first-hit-from-each
      [query engines]
      (let [futs (mapv (fn [engine]
                         (future (search-for query engine)))
                       engines)]
        (mapv #(println (deref %)) futs)))

с результатом:

Testing tst.demo.core
https://www.google.com/search?q%3Dgray+cat
https://www.bing.com/search?q%3Dgray+cat
"Elapsed time: 2003.770331 msecs"
06.07.2020
  • Я предлагаю отредактировать ваш ответ, чтобы включить только правильную часть. Если вы настаиваете, вы можете оставить свой предыдущий неправильный ответ в качестве дополнения или что-то в этом роде, но я бы этого не сделал. Stack Overflow — это хранилище кураторских вопросов и ответов, а не дневник: то, что вы когда-то были неправы, не означает, что это нужно показывать так заметно. 06.07.2020
  • Почему vec вместо doall, чтобы смягчить ленивую часть? Кроме того, с mapv вместо map не должно ли быть 2 секунды, если вы разрешите всем фьючерсам начинать диспетчеризацию через doall до mapv? 06.07.2020
  • @PalaceChan: каждый вызов search-for будет приостанавливаться на 2 секунды. @AMalloy Я подумал о том, чтобы сделать такое же редактирование (то есть стереть / скрыть мою ошибку), но подумал, что было бы более информативно показать, как легко любой может ошибиться в порядке выполнения, когда в игру вступает ленивая оценка. Это особенно важно, когда задействован ввод-вывод, например, чтение БД или вывод файла/печати. 06.07.2020
  • Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..