Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pol_Grem_-_ANSI_Common_Lisp_High_tech_-_2012.pdf
Скачиваний:
27
Добавлен:
12.03.2016
Размер:
4.85 Mб
Скачать

234 Глава 13. Скорость

(defconstant pool (make-array 1000 :fill-pointer t))

(dotimes (i 1000)

(setf (aref pool i) (make-ship)))

(defconstant harbor (make-hash-table :size 1100 :test #’eq))

(defun enter (n f d)

(let ((s (if (plusp (length pool)) (vector-pop pool)

(make-ship))))

 

(setf (ship-name s)

n

(ship-flag s)

f

(ship-tons s)

d

(gethash n harbor) s)))

(defun find-ship (n) (gethash n harbor))

(defun leave (n)

(let ((s (gethash n harbor))) (remhash n harbor) (vector-push s pool)))

Рис. 13.5. Порт, версия­ 2

Исполь­зуя­ пулы,­ мы собст­вен­но­руч­но­ выпол­ня­ем­ часть рабо­ты­ по управ­ лению­ памя­тью­. Сдела­ет­ ли это нашу­ програм­му­ быст­рее,­ зави­сит­ от то­ го, как конкрет­ная­ реали­за­ция­ управля­ет­ памя­тью­. Грубо­ гово­ря,­ ис­ поль­зова­ние­ пулов­ оправ­да­но­ лишь в реали­за­ци­ях­ с прими­тив­ны­ми­ сборщи­ка­ми­ мусо­ра,­ а также­ в прило­же­ни­ях­ реаль­но­го­ време­ни,­ где не­ предска­зуе­мый­ запуск­ сборщи­ка­ мусо­ра­ может­ вызвать­ пробле­мы­.

13.6.Быстрые операторы

Внача­ле­ главы­ было­ сказа­но,­ что Лисп – это, по сути,­ два разных­ языка­. Если­ вы пригля­ди­тесь­ к дизай­ну­ Common Lisp, то увиди­те,­ что часть его опера­то­ров­ предна­­значе­на­ для уско­ре­ния­ выпол­не­ния,­ а другая­ часть – для удобст­ва­ разра­бот­ки­.

Напри­мер,­ для досту­па­ к элемен­ту­ векто­ра­ суще­ст­ву­ют­ три опера­то­ра:­ elt, aref, svref. Такое­ разно­об­ра­зие­ позво­ля­ет­ выжать­ из програм­мы­ макси­мум­ произ­во­ди­тель­но­сти­. На тех участ­ках,­ где важ­на скорость,­ исполь­зуй­те­ svref вместо­ elt, кото­рая­ рабо­та­ет­ и с масси­ва­ми,­ и со спи­ сками­.

Для рабо­ты­ со списка­ми­ эффек­тив­нее­ исполь­зо­вать­ специа­ли­зи­ро­ван­­ ную функцию­ nth, неже­ли­ elt. Лишь одна­ функция,­ length, не имеет­ анало­гов­ для разных­ типов­. Поче­му­ в Common Lisp нет отдель­ной­ вер­ сии этой функции­ для списков?­ Пото­му­ что програм­ма,­ вы­полняю­щая­

13.6. Быстрые операторы

235

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

Другая­ пара­ похо­жих­ функций ­– eql и eq. Первый­ преди­кат­ прове­ря­ет­ на идентич­ность,­ второй ­– на одина­ко­вое­ разме­ще­ние­ в памя­ти­. Второй­ преди­кат­ обеспе­чи­ва­ет­ большую­ эффек­тив­ность,­ одна­ко­ его стоит­ ис­ поль­зовать,­ если­ только­ вам зара­нее­ извест­но,­ что аргу­мен­ты­ не явля­­ ются­ числа­ми­ или знака­ми­. Два объ­екта­ равны­ с точки­ зрения­ eq, если­ они имеют­ одина­ко­вое­ разме­ще­ние­ в памя­ти­. Числа­ и знаки­ не обяза­ны­ распо­ла­гать­ся­ в каком­-либо­ опре­де­лен­ном­ месте­ в памя­ти,­ поэто­му­ eq к ним непри­ме­ни­ма­ (хотя­ во многих­ реали­за­ци­ях­ eq рабо­та­ет­ с типом­ fixnum). Для любых­ других­ аргу­мен­тов­ eq будет­ рабо­тать­ анало­гич­но­ eql.

Разу­ме­ет­ся,­ быст­рее­ всего­ выпол­нять­ сравне­ние­ объек­тов­ с помо­щью­ eq, так как при этом Лиспу­ доста­точ­но­ сравнить­ лишь два указа­те­ля­. Это значит,­ что хеш-табли­цы­ с тесто­вой­ функци­ей­ eq (см. рис. 13.5) обес­ печи­ва­ют­ самый­ быст­рый­ доступ­. В таких­ табли­цах­ gethash хеши­ру­ет­ лишь указа­те­ли­ и даже­ не смотрит,­ на что они указы­ва­ют­. Поми­мо­ ско­ рости­ досту­па­ с хеш-табли­ца­ми­ связан­ еще один момент­. Исполь­зо­ва­­ ние eq- и eql-таблиц­ приво­дит­ к допол­ни­тель­ным­ издерж­кам­ в случае­ приме­не­ния­ копи­рую­щей­ сборки­ мусо­ра,­ посколь­ку­ после­ каждой­ сбор­ ки хеши­ таких­ таблиц­ должны­ быть пере­счи­та­ны­. Если­ это вызы­ва­ет­ пробле­мы,­ лучше­ исполь­зо­вать­ eql-табли­цы­ и числа­ типа­ fixnum в каче­­ стве­ ключей­.

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

(apply #’+ ’(1 2 3))

эффек­тив­нее­ исполь­зо­вать­ следую­щее­ выра­же­ние:­

(reduce #’+ ’(1 2 3))

Кроме­ того,­ важно­ не только­ вызы­вать­ правиль­ные­ функции,­ но и вы­ зывать­ их правиль­но­. Необя­за­тель­ные­ аргу­мен­ты,­ аргу­мен­ты­ по ключу­ и оста­точ­ные­ аргу­мен­ты­ доро­го­стоя­щи­. В случае­ обычных­ аргу­мен­тов­ вызы­ваю­щая­ сторо­на­ кладет­ их в зара­нее­ извест­ное­ вызван­ной­ функ­ ции место,­ в то время­ как для других­ аргу­мен­тов­ требу­ет­ся­ допол­ни­­ тельная­ обра­бот­ка­ на этапе­ выпол­не­ния­. Хуже­ всего­ в этом отно­ше­нии­ дело­ обсто­ит­ с аргу­мен­та­ми­ по ключу­. Для встроен­ных­ функций,­ ис­ пользую­щих­ эти аргу­мен­ты,­ хоро­шие­ компи­ля­то­ры­ исполь­зу­ют­ особые­ подхо­ды,­ чтобы­ полу­чить­ быст­рый­ код2. Но в ваших­ собст­вен­ных­ функ­ циях­ реко­мен­ду­ет­ся­ избе­гать­ исполь­зо­ва­ния­ аргу­мен­тов­ по ключу­ на

1Это утвер­жде­ние­ авто­ра­ явля­ет­ся­ доволь­но­ сомни­тель­ным­. По крайней­ ме­ ре, для ряда­ совре­мен­ных­ реали­за­ций­ на приве­ден­ном­ приме­ре­ наблю­да­ет­­ ся выра­жен­ный­ проти­во­по­лож­ный­ эффект­. – Прим. перев­.

2Речь идет о таких­ техни­ках,­ как, напри­мер,­ прямое­ коди­ро­ва­ние­ (open co­ ding). – Прим. перев­.

236

Глава 13. Скорость

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

Инди­ви­ду­аль­ные­ компи­ля­то­ры­ иногда­ приме­ня­ют­ особые­ опти­ми­за­­ ции. Напри­мер,­ неко­то­рые­ могут­ опти­ми­зи­ро­вать­ вызов­ case с цело­чис­­ лен­ными­ ключа­ми­ в неболь­шом­ диапа­зо­не­ их значе­ний­. Как прави­ло,­ узнать­ подроб­нее­ о специ­фи­че­ских­ опти­ми­за­ци­ях­ в вашей­ реали­за­ции­ вы може­те­ из доку­мен­та­ции­ к ней.

13.7. Две фазы разработки

Если­ в вашем­ прило­же­нии­ произ­во­ди­тель­ность­ имеет­ перво­сте­пен­ное­ значе­ние,­ в каче­ст­ве­ одно­го­ из реше­ний­ попро­буй­те­ пере­пи­сать­ крити­­ ческие­ участ­ки­ на низко­уров­не­вом­ языке,­ напри­мер­ на С или ассемб­ле­­ ре. Такой­ подход­ приме­ним­ не только­ к Лиспу,­ но и к любо­му­ высо­ко­­ уровне­во­му­ языку ­– крити­че­ские­ участ­ки­ програм­мы­ на С часто­ пере­­ писы­ва­ют­ся­ на ассемб­ле­ре, ­– но чем абст­ракт­нее­ язык, тем больше­ пре­ имуществ­ дает­ разбие­ние­ разра­бот­ки­ на две фазы­.

Стандарт­ Common Lisp не описы­ва­ет­ меха­низм­ инте­гра­ции­ кода,­ напи­­ санно­го­ на других­ язы­ках. Эта зада­ча­ полно­стью­ ложит­ся­ на плечи­ раз­ работ­чи­ков­ конкрет­ной­ реали­за­ции,­ и почти­ все из них так или иначе­ реша­ют­ ее.1

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

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

Каза­лось­ бы, если­ специ­фи­ка­ции­ были­ хоро­ши­ми­, то програм­ми­ро­ва­­ ние заклю­ча­лось­ бы лишь в коди­ро­ва­нии­. Это пороч­ная­ идея, хотя­ и распро­стра­нен­ная­. Иссле­до­ва­тель­ская­ компо­нен­та­ важ­на уже хотя­ бы пото­му,­ что любая­ специ­фи­ка­ция­ по опре­де­ле­нию­ содер­жит­ недос­­ татки­. Иначе­ она не была­ бы специ­фи­ка­ци­ей­.

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

1Библио­те­ки­ CFFI и UFFI предос­­тавля­ют­ обобщен­ный­ меха­низм­ исполь­зо­­ вания­ интер­фей­са­ с внешним­ кодом­ (foreign function interface), доступ­ный­ в разных­ реали­за­ци­ях­. – Прим. перев­.

Итоги главы

237

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

В прило­же­ни­ях,­ подра­зу­ме­ваю­щих­ значи­тель­ное­ коли­че­ст­во­ иссле­до­­ ваний­ (объем­ кото­рых­ опять же превзой­дет­ ожида­ния),­ имеет­ смысл разде­лять­ реали­за­цию­ програм­мы­ на две фазы­. Проме­жу­точ­ный­ ре­ зультат­ после­ первой­ фазы­ не будет­ оконча­тель­ным­. Напри­мер,­ при вая­ нии бронзо­вой­ скульпту­ры­ приня­то­ делать­ первый­ набро­сок­ из глины,­ кото­рый­ затем­ исполь­зу­ет­ся­ для созда­ния­ формы,­ в кото­рую­ будет­ отли­­ та скульпту­ра­ из бронзы­.° И хотя­ в завер­шен­ной­ скульпту­ре­ глины­ не оста­ет­ся,­ эффект­ от ее исполь­зо­ва­ния­ все равно­ заме­тен­. Теперь­ вооб­ра­­ зите,­ насколь­ко­ сложней­ была­ бы та же зада­ча­ при нали­чии­ лишь слит­ ка бронзы­ и зуби­ла­. По тем же причи­нам­ удобнее­ напи­сать­ програм­му­ на Лиспе,­ а затем­ пере­пи­сать­ на С, чем писать­ ее на С с само­го­ нача­ла­.

Итоги главы

1.К опти­ми­за­ции­ не следу­ет­ присту­пать­ раньше­ време­ни­. Основ­ное­ внима­ние­ нужно­ уделять­ узким­ места­м­ и начи­нать­ следу­ет­ с выбо­ра­ опти­маль­но­го­ алго­рит­ма­.

2.Доступ­но­ пять пара­мет­ров­ управле­ния­ компи­ля­ци­ей­. Они могут­ ус­ танав­ли­вать­ся­ как глобаль­ны­ми,­ так и локаль­ны­ми­ декла­ра­ция­ми­.

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

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

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

6.В ряде­ ситуа­ций­ полез­но­ брать объек­ты­ из предва­ри­тель­но­ создан­­ ного­ пула­.

7.Неко­то­рые­ части­ Лиспа­ предна­зна­че­ны­ для быст­рой­ рабо­ты,­ неко­то­­ рые – для гибкой­.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]