Понимание внутреннего продукта APL

Вот отрывок из книги Mastering Dyalog APL из главы " Внутренние продукты":

HMS is a variable which contains duration in Hours, Minutes, and Seconds: HMS ← 3 44 29 Chapter J – Operators 397

We would like to convert it into seconds. We shall see 3 methods just now, and a 4th
 method 
will be given in another chapter. 
    A horrible solution                        (3600×HMS[1]) + (60×HMS[2]) + HMS[3] 
    A good APL solution                        +/ 3600 60 1 × HMS 
    An excellent solution with Inner Product   3600 60 1 +.× HMS 

Затем говорится, что второе и третье решения эквивалентны с точки зрения количества набранных символов и производительности.

Насколько я понимаю, программисты APL, как правило, должны как можно больше использовать Inner Product, а также Outer Product. Это верно?

Можете ли вы привести пример, когда использование Inner Product приведет к увеличению производительности? Что именно происходит, когда я использую Inner Product (на более низком уровне)? Является ли первое решение, представленное ниже, ужасным только потому, что оно не использует синтаксис APL надлежащим образом или оно действительно имеет худшую производительность?

Я знаю, что есть несколько вопросов, но в целом я хочу задать вопрос о том, как работают Внутренние / Внешние продукты и когда именно их должен использовать программист APL.

3 ответа

Решение

Программисты APL, как правило, должны максимально использовать Внутренний продукт, а также Внешний продукт. Это верно?

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

В этом конкретном случае 60⊥HMS еще более лаконичен и эффективен, чем внутренний продукт.

Можете ли вы привести пример, когда использование Inner Product приведет к увеличению производительности?

Как это обычно бывает в программировании на основе массивов, прирост производительности достигается за один раз. Большинство функций APL являются неявными циклами - их реализация использует счетчик, ограничение для него и шаг приращения. Чем короче ваш код, тем лучше, потому что он не только легче держать в голове, но и эффективнее, поскольку интерпретатору нужно меньше проходить через данные. Некоторые реализации делают объединение циклов в попытке уменьшить эти издержки. У некоторых есть идиома- определенные комбинации загогулин в специальном случае переводчика. Выполнение действий за один раз также позволяет интерпретатору выполнять умные оптимизации, такие как использование набора инструкций SSE или графических процессоров.

Возвращаясь к внутреннему продукту, давайте возьмем пример A f.g B где A а также B векторы и посмотрим, как f а также g применяются (в Дьялоге):

      f←{⎕←(⍕⍺),' f ',⍕⍵ ⋄ ⍺+⍵}
      g←{⎕←(⍕⍺),' g ',⍕⍵ ⋄ ⍺×⍵}
      0 1 2 3 4 f.g 5 6 7 8 9
4 g 9
3 g 8
24 f 36
2 g 7
14 f 60
1 g 6
6 f 74
0 g 5
0 f 80
80

Из вышесказанного видно, что звонки f а также g чередуются. Переводчик яблок f и уменьшает на g одновременно, за один проход, избегая создания временного массива, как f/ A g B сделал бы.

Другой пример: http://archive.vector.org.uk/art10500200

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

      )copy dfns.dws cmpx
      ⍝ or: ")copy dfns cmpx" if you are using Windows
      HMS ← 3 44 29
      cmpx '(3600×HMS[1]) + (60×HMS[2]) + HMS[3]' '+/ 3600 60 1 × HMS' '3600 60 1 +.× HMS' '60⊥HMS'
  (3600×HMS[1]) + (60×HMS[2]) + HMS[3] → 2.7E¯6 |   0% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
  +/ 3600 60 1 × HMS                   → 9.3E¯7 | -66% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
  3600 60 1 +.× HMS                    → 8.9E¯7 | -68% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
  60⊥HMS                               → 4.8E¯7 | -83% ⎕⎕⎕⎕⎕⎕⎕

Мы проделали работу по оптимизации как +/, так и +. ×.

MBaas прав в том, что бывает, что +/ в этом случае немного лучше, чем +. ×

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

"Ужасное" решение считается плохим, так как оно не использует массивное мышление.

С Уважением,

Винс, Dyalog Поддержка

Проблема с обобщением заключается в том, что они могут быть неправильными, но, как я бы сказал, использование внутренних и внешних продуктов будет способствовать удобочитаемости и производительности;-)

Теперь посмотрим на вещь на практике:

`] performance.RunTime (3600 × HMS [1]) + (60 × HMS [2]) + HMS [3] -repeat =100000

  • Сравнительный анализ "(3600×HMS[1])+(60×HMS[2])+HMS[3]", повтор =100000 Эксп. ЦП (среднее значение): 0,001558503836 Истекло: 0,001618446292

    ] performance.RunTime '+ / 3600 60 1 × HMS' -repeat =100000

  • Бенчмаркинг "+/ 3600 60 1 × HMS", повтор =100000 Эксп. ЦП (ср.): 0.0004698496481 Прошло: 0.0004698496481 `

Это большая разница - если вы повторите это достаточно много раз, чтобы его можно было измерить;-) Но, конечно, с большим набором данных преимущество становится более заметным! Давайте также посмотрим на 3 варианта:

`] performance.RunTime '3600 60 1 +. × HMS' -repeat =100000

  • Бенчмаркинг "3600 60 1 +.× HMS", повтор =100000 Эксп. ЦП (среднее значение): 0.0004698496481 Истекло: 0.000439859245
    `

Здесь нет никакой разницы, но опять же - с "реальными данными" (большим массивом) вы должны увидеть гораздо более четкую разницу. Я думаю, что простое объяснение состоит в том, что внутренний продукт подобен одному "утверждению" для интерпретатора, тогда как первый вариант имеет 3 одинарных умножения, индексации и должен учитывать приоритеты (скобки), а затем суммировать этот вектор, который звучит как много sweat;-) 2-е утверждение имеет только одно умножение (для вектора), поэтому оно уже облегчает несколько шагов, и внутренний продукт позволяет интерпретатору, возможно, объединить некоторые из его внутренних операций, чтобы выполнять свою работу еще быстрее.

НО теперь вот сюрприз: v1←(10000/3600 60 1) ⋄v2← 10000/HMS ] Производительность.RunTime '+/v1 × v2' 'v1 +.× v2' -repeat=100000 -сравнить

  +/v1 × v2 → 6.0E¯5 |  0% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕   
  v1 +.× v2 → 6.3E¯5 | +5% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕ 

Я ожидал, что более крупные аргументы помогут сделать преимущество в производительности последнего выражения более заметным, но на самом деле победил второй. Может быть, Dyalog оптимизировал случай № 2 больше, чем № 3...;-)

Другие вопросы по тегам