LXF134:StarBasic

Материал из Linuxformat.

Перейти к: навигация, поиск
Поль­зо­ва­тель­ские функ­ции До­бавь­те элек­трон­ным таб­ли­цам не­дос­таю­щий функ­цио­нал

Содержание

OOo Calc: Оптимизация

OOo Calc
По­сле то­го, как функ­ция на­пи­са­на и ра­бо­та­ет пра­виль­но, на­до до­бить­ся, что­бы она ра­бо­та­ла бы­ст­ро. Алек­сандр Мад­жу­гин зна­ет, как это­го до­бить­ся.

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

В этой ста­тье мы нач­нём го­во­рить об оп­ти­ми­за­ции вы­чис­лений в Calc, за­тра­ги­вая во­про­сы оп­ти­ми­за­ции ко­да в функ­ци­ях Basic, при­менитель­но к реа­ли­за­ции, встро­ен­ной в OpenOffice.org. Ста­тья ока­жет­ся по­лез­ной не толь­ко тем, кто соз­да­ёт поль­зо­ва­тель­ские функ­ции Calc, но и всем, кто занима­ет­ся раз­ра­бот­кой мак­ро­сов для OOo.

Дат­чик ско­ро­сти

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

По­смот­рим на сле­дую­щий код:

Sub Chronometr
	 Dim lStartTime As Long ' Вре­мя за­пус­ка
	 Dim lResult As Long ' Вре­мя ра­бо­ты
	 lStartTime = getSystemTicks
	 MyFunction
	 lResult = getSystemTicks - lStartTime
	 Print lResult
End Sub

Он из­ме­ря­ет вре­мя од­но­крат­но­го вы­чис­ления функ­ции MyFunction пу­тём на­хо­ж­дения раз­но­сти зна­чений счёт­чи­ка getSystemTicks до вы­зо­ва функ­ции и по­сле его за­вер­шения.

Как пра­ви­ло, од­но­го вы­чис­ления недоста­точ­но – ка­ки­ми бы мед­лен­ны­ми ни бы­ли ин­тер­пре­та­то­ры, ско­рость всё рав­но доста­точ­но вы­со­ка, и шанс, что ре­зуль­та­том за­ме­ра бу­дет 0, весь­ма ве­лик. По­это­му прак­ти­че­ски все­гда при­хо­дит­ся ис­поль­зо­вать цикл с мно­го­крат­ным вы­чис­лением функ­ции: чем он длиннее, тем мень­ше по­греш­ность и на­клад­ные из­держ­ки из­ме­рения. На прак­ти­ке за­мер дол­жен длить­ся не мень­ше 8–10 се­кунд.

Рас­смот­рим прак­ти­че­ский при­мер, а на­шим под­опыт­ным ста­нет функ­ция FXOR из пре­ды­ду­щей ста­тьи – Листинг 1. Для из­ме­рения ско­ро­сти её ра­бо­ты пре­об­ра­зу­ем про­це­ду­ру Chronometr таким образом:

Sub Chronometr
	 Dim lStartTime As Long ' Вре­мя за­пус­ка
	 Dim lResult As Long ' Вре­мя ра­бо­ты
	 Dim lFuncResult As Long ' Пе­ре­мен­ная для приё­ма ре­зуль­та­та
	 Dim l As Long
	 lStartTime = getSystemTicks
	 For l = 1 To 100000
		 lFuncResult = FXOR(255,l)	
	 Next
	 lResult = getSystemTicks - lStartTime
	 Print lResult
End Sub

Мне по­на­до­би­лось 100 000 ша­гов цик­ла для то­го, что­бы вре­мя ра­бо­ты на мо­ём но­ут­бу­ке при­бли­зи­лось к 10 се­кун­дам. Ес­ли у вас про­из­во­ди­тель­ная ма­ши­на, вам мо­жет по­тре­бо­вать­ся боль­ше. Для приё­ма ре­зуль­та­та я соз­дал спе­ци­аль­ную пе­ре­мен­ную lFuncResult: это не обя­за­тель­но, но я люб­лю, чтобы всё бы­ло «по-честному». В ка­че­стве вто­ро­го ар­гу­мен­та FXOR я ис­поль­зо­вал счёт­чик цик­ла – это удоб­но, осо­бен­но ес­ли вре­мя ра­бо­ты ва­шей функ­ции мо­жет за­ви­сеть от зна­чения ар­гу­мен­тов.

Для по­вы­шения че­ст­но­сти за­ме­ра иногда бы­ва­ет по­лез­но вы­чис­лить вре­мя хо­ло­сто­го хо­да тесто­вой про­це­ду­ры. Про­сто за­ком­мен­ти­руй­те стро­ку lFuncResult = FXOR(255,l), вы­полните несколь­ко за­пусков, и, за­помнив вре­мя ра­бо­ты про­це­ду­ры, вы­чти­те его из конеч­но­го ре­зуль­та­та, ли­бо до­бавь­те в на­ча­ло функ­ции сле­дую­щий код:

lStartTime = getSystemTicks
For l = 1 To 100000
Next
lResult = getSystemTicks - lStartTime

Стро­ку lResult = getSystemTicks — lStartTime сле­ду­ет за­менить на lResult = getSystemTicks — lStartTime — lResult. Тогда хо­ло­стой ход бу­дет вы­чи­тать­ся из ре­зуль­та­та ав­то­ма­ти­че­ски. Дру­гим спо­­-со­бом снижения на­клад­ных из­дер­жек ра­бо­ты цик­ла яв­ля­ет­ся мно­го­крат­ный вы­зов про­це­ду­ры внут­ри него.

Для пе­ре­гру­жен­ных функ­ций важ­ным яв­ля­ет­ся ещё и тип ис­поль­зуе­мых в тесте ар­гу­мен­тов. На­при­мер, вы­зов FXOR толь­ко для це­лых дал у ме­ня ре­зуль­тат око­ло 10 000 мил­ли­се­кунд, а вы­зов её же для строк дли­ной 6 сим­во­лов дал уже в среднем 47 000 мил­ли­се­кунд. Ста­рай­тесь за­ме­рять об­щую про­из­во­ди­тель­ность функ­ции в усло­ви­ях, близ­ких к ре­аль­ным. На­при­мер, ес­ли пред­по­ла­га­ет­ся, что FXOR бу­дет ис­поль­зо­вать­ся для це­лых при­мер­но в 75 % слу­ча­ев, а для строк средней дли­ной 6 сим­во­лов толь­ко в 25 % слу­ча­ев, то код внут­ри цик­ла дол­жен вы­гля­деть при­мер­но так:

lFuncResult = FXOR(65535,l)
lFuncResult = FXOR(0,l)
lFuncResult = FXOR(l,l)
sFuncResult = FXOR(“djkely”,”saljcx”)

Эти ре­ко­мен­да­ции ка­са­ют­ся толь­ко об­щей про­из­во­ди­тель­но­сти. При оп­ти­ми­за­ции кон­крет­но­го уча­ст­ка ко­да, на­про­тив, сле­ду­ет снизить все из­держ­ки на вы­чис­ления вне его. Для дости­жения дан­ной це­ли ис­поль­зуй­те знания о струк­ту­ре ко­да и кон­ст­рук­ции сво­его ал­го­рит­ма. На­при­мер, при оп­ти­ми­за­ции об­ра­бот­ки строк в на­шем при­ме­ре во­об­ще не сто­ит вы­зы­вать са­му функ­цию FXOR, да­бы из­бе­жать лишних из­дер­жек на пе­ре­груз­ку, ко­то­рые по­сто­ян­ны. Сле­ду­ет вы­зы­вать сра­зу её под­функ­цию SXOR. В дан­ном слу­чае это по­мо­га­ет снизить вре­мя ра­бо­ты теста поч­ти на 20 %.

Оп­ти­ми­за­ция ко­да

Пе­рей­дем непо­сред­ствен­но к оп­ти­ми­за­ции, и начнем с под­функ­ций. Внима­тель­но по­смот­рим на SXOR. Мож­но ли здесь что-то улуч­шить? С пер­во­го взгля­да – вро­де бы нет. Но вот что вый­дет, ес­ли мы при­меним лишь па­ру из са­мых важ­ных пра­вил оп­ти­­-ми­за­ции:

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

Ка­кие мы мо­жем вы­де­лить ча­ст­ные слу­чаи? Во-пер­вых, когда вто­рой ар­гу­мент ра­вен по длине или длиннее пер­во­го. В этом слу­чае ста­но­вит­ся лишней опе­ра­ция де­ления зна­чения счёт­чи­ка по мо­ду­лю дли­ны вто­ро­го ар­гу­мен­та – он и так не вый­дет за пре­де­лы стро­ки. Со­от­вет­ствен­но, вы­чис­ление оче­ред­но­го сим­во­ла ре­зуль­та­та внут­ри цик­ла мож­но пе­репи­сать так: sResult = sResult & Chr(ASC(Mid(sFirst,l,1)) XOR ASC(Mid(sSecond,l,1))), миними­зи­ро­вав чис­ло опе­ра­ций внут­ри цик­ла.

Во-вто­рых, дли­на вто­ро­го ар­гу­мен­та мо­жет быть рав­на одно­му сим­во­лу. Здесь во­об­ще ста­но­вят­ся не нуж­ны функ­ции Mid и ASC внут­ри цик­ла, и опе­ра­цию на­хо­ж­дения ко­да сим­во­ла вто­рой стро­ки мож­но вынести:

iSC = ASC(sSecond)
For l = 1 To lLF
	 sResult = sResult & Chr(ASC(Mid(sFirst,l,1)) XOR iSC)
Next

Ну и тре­тий слу­чай – когда вто­рой ар­гу­мент ко­ро­че пер­во­го, но длиннее од­но­го сим­во­ла. Здесь код останет­ся неиз­мен­ным. В ито­ге функ­ция при­мет вид, при­ве­ден­ный в Листин­ге 2.

Рис. 1. Рис. 1. Ре­зуль­та­ты тес­ти­ро­ва­ния про­из­во­ди­тель­но­сти оп­ти­ми­зи­ро­ван­ной функ­ции SXOR.

Как вид­но, функ­ция ста­ла длиннее. В неё до­ба­ви­лись но­вые опе­ра­ции сравнения. Ну, а что же с про­из­во­ди­тель­но­стью? Со­глас­но рис. 1, мо­ди­фи­ка­ция принес­ла нам бо­лее чем 13 % рост про­из­во­ди­тель­но­сти в син­те­ти­че­ском тесте (по 5 000 рас­чё­тов для на­бо­ров с дли­ной вто­ро­го ар­гу­мен­та 1, 3 и 16 сим­во­лов и дли­ной пер­во­го ар­гу­мен­та 16 сим­во­лов). При этом худ­ший по­ка­за­тель мо­ди­фи­ци­ро­ван­ной функ­ции все­го -2,6 % для третье­го слу­чая (15 000 рас­чё­тов; дли­на пер­во­го ар­гу­мен­та – 16 сим­во­лов, а вто­ро­го – 3). Не та­кая уж боль­шая по­те­ря, осо­бен­но ес­ли учесть, что для пер­во­го слу­чая (15 000 рас­чё­тов; дли­на пер­во­го ар­гу­мен­та – 16 сим­во­лов, а вто­ро­го – 1) мы смог­ли до­бить­ся при­роста про­из­во­ди­тель­но­сти бо­лее чем в 30 %.

Для че­го я так под­роб­но вы­полнил, а те­перь опи­сы­ваю этот тест? Де­ло в том, что здесь мы про­во­ди­ли спе­ци­аль­ную оп­ти­ми­за­цию ко­да, основ­ной про­бле­мой ко­то­рой яв­ля­ет­ся то, что она да­ёт не толь­ко рост про­из­во­ди­тель­но­сти для от­дель­ных слу­ча­ев ра­бо­ты ко­да, но и (за­частую) снижение про­из­во­ди­тель­но­сти для неко­то­рых дру­гих.

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

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

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

В об­щем слу­чае

Для об­щей оп­ти­ми­за­ции обыч­но луч­ше ис­поль­зо­вать син­те­ти­че­ские тесты, наи­бо­лее близ­кие к ре­аль­ным усло­ви­ям. Для при­ме­ра здесь мы пред­по­ло­жим, что на­ша функ­ция 'FXOR бу­дет ис­поль­зо­вать­ся пре­иму­ще­ствен­но сле­дую­щим об­ра­зом: в по­ло­вине случа­ев оба вхо­дя­щих па­ра­мет­ра бу­дут це­лы­ми по­ло­жи­тель­ны­ми чис­ла­ми, а в по­ло­вине – стро­ка­ми. При­чём для строк из­вест­но, что дли­на пер­во­го па­ра­мет­ра в среднем бу­дет рав­на 16 зна­кам, а дли­на вто­ро­го в тре­ти слу­ча­ев бу­дет рав­на длине пер­во­го (то есть тем же 16 зна­кам), ещё в тре­ти – од­но­му зна­ку, и ещё в тре­ти бу­дет пе­ре­мен­ной от 1 до 4 зна­ков.

Для эму­ля­ции по­следней тре­ти слу­ча­ев ис­поль­зо­вания FXOR со стро­ка­ми при­меним пре­об­ра­зо­вание зна­чения счёт­чи­ка к стро­ко­во­му ти­пу – Cstr(l). При этом не за­бу­дем до­ба­вить стро­ку sFuncResult = Cstr(l) в цикл из­ме­рения вре­мени хо­ло­сто­го хо­да тесте­ра, да­бы уве­ли­чить точ­ность из­ме­рения вре­мени ра­бо­ты функ­ции.

Код же внут­ри цик­ла из­ме­рения мо­жет получиться при­мер­но сле­дую­щим:

Рис. 2. Рис. 2. Рас­пре­де­ле­ние вре­ме­ни на вы­зов и вы­чис­ле­ние под­функ­ции в функ­ции COMBINE.

lFuncResult = FXOR(65535,0)
lFuncResult = FXOR(65535,l)
lFuncResult = FXOR(0,l)
sFuncResult = FXOR(“djkelyqwertyjlkr”,”s”)
sFuncResult = FXOR(“djkelyqwertyjlkr”,CStr(l))
sFuncResult = FXOR(“djkelyqwertyjlkr”,”salghkmvtrdljtra”)

Вы­полним за­ме­ры для ис­ход­ной, неоп­ти­ми­зи­ро­ван­ной функ­ции. У ме­ня в син­те­ти­че­ском тесте по­лу­чи­лось в среднем 28 859 мил­ли­се­кунд.

Мож­но пе­ре­хо­дить к оп­ти­ми­за­ции. По­оче­рёд­но раз­бе­рём все ис­поль­зо­ван­ные приё­мы.

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

По­это­му про­сто пе­ренесём со­дер­жи­мое на­ших под­функ­ций NXOR и SXOR в места их вы­зо­ва из ро­ди­тель­ской функ­ции FXOR и вне­сём необ­хо­ди­мые из­менения в име­на пе­ре­мен­ных. В дан­ном слу­чае это даст нам око­ло 6 % при­роста про­из­во­ди­тель­но­сти. Ес­ли вам ка­жет­ся, что это немно­го, то вспомните функ­цию COMBINE из пре­ды­ду­щей ста­тьи, где вы­зов под­функ­ции мо­жет про­ис­хо­дить до 30 раз за вы­чис­ление. Здесь вре­мя на вы­зов под­функ­ций и непо­сред­ствен­ное вы­чис­ление ре­зуль­та­та рас­пре­де­ле­но при­бли­зи­тель­но так, как на диа­грам­ме на рис. 2. Не слиш­ком ли жир­ный ку­сок пи­ро­га мы от­да­ём за воз­мож­ность ис­поль­зо­вать под­функ­цию, в усло­ви­ях нехват­ки вре­мени?

Да­лее мож­но пе­ре­хо­дить к бо­лее тон­кой на­строй­ке, ко­то­рая в дан­ном слу­чае сво­дит­ся к знанию осо­бен­но­стей ис­поль­зо­вания функ­ции и уст­рой­ства ин­тер­пре­та­то­ра Basic в OpenOffice.org. И это наи­бо­лее важ­ная часть дан­ной ста­тьи, так как этих све­дений вы не встре­ти­те в дру­гих пуб­ли­ка­ци­ях, по­свя­щён­ных оп­ти­ми­за­ции ко­да.

Вспомним, что в функ­ци­ях Calc невоз­мож­но пе­ре­дать ка­кой ли­бо ар­гу­мент, опустив один из пред­ше­ствую­щих. Сле­до­ва­тель­но, про­вер­ка пе­ре­да­чи па­ра­мет­ра vFirst яв­ля­ет­ся из­лишней, так как ес­ли он опу­щен, то опу­щен и па­ра­метр vSecond; а зна­чит, код про­вер­ки пе­ре­да­чи па­ра­мет­ров мож­но из­менить на сле­дую­щий:

If IsMissing (vSecond) Then ' про­ве­ря­ем пе­ре­да­ны ли па­ра­мет­ры
	 fXOR = “#NOTARG!” ' Ар­гу­мен­тов слиш­ком ма­ло
	 Exit Function
End If

Да­лее, при­ме­ним зна­ние об од­ной не са­мой при­ят­ной осо­бен­но­сти StarBasic. Рас­смот­рим сле­дую­щую кон­ст­рук­цию:

If ус­ло­вие1 AND ус­ло­вие2 Then
	 код
End If

В боль­шин­стве ру­ко­водств по дру­гим язы­кам вы про­чтё­те, что усло­вие2 не бу­дет про­ве­рять­ся, ес­ли усло­вие1 лож­но, так как в этом слу­чае ре­зуль­тат всей кон­ст­рук­ции от ре­зуль­та­та усло­вия2 не за­ви­сит – она всё рав­но останет­ся лож­ной.

Рис. 3. Рис. 3. Ре­зуль­тат тес­ти­ро­ва­ния функ­ции FXOR.

В StarBasic это не так: усло­вие2 здесь бу­дет про­ве­ре­но в лю­бом слу­чае, неза­ви­си­мо от ре­зуль­та­та усло­вия1. По­это­му, что­бы не те­рять про­из­во­ди­тель­но­сти, в StarBasic вы­ше­при­ве­дён­ную кон­ст­рук­цию сле­ду­ет пе­репи­сать так:

If ус­ло­вие1 Then
	 If ус­ло­вие2 Then
		 код
	 End If
End If

Про­де­ла­ем эту опе­ра­цию с кон­ст­рук­ция­ми If IsNumeric(vFirst) And IsNumeric(vSecond) Then... и If VarType(vFirst)=8 And VarType(vSecond)=8 Then.... То, что по­лу­чи­лось в ре­зуль­та­те этих пре­об­ра­зо­ваний, пред­став­ле­но в Листин­ге 3. Та­кой код в усло­ви­ях опи­сан­но­го вы­ше теста станет про­из­во­ди­тельнее ещё при­бли­зи­тель­но на 9 %.

Те­перь по­ра сравнить про­из­во­ди­тель­ность по­лу­чен­ной на­ми функ­ции и функ­ции, при­ве­дён­ной в са­мом на­ча­ле ста­тьи.

Сравнивать бу­дем в трёх тестах – толь­ко чис­ла, толь­ко стро­ки и син­те­ти­че­ский тест. Для по­следнего ис­поль­зу­ем вы­ше­при­ве­дён­ные усло­вия. Для чи­сел за­ком­мен­ти­ру­ем стро­ки с вы­зо­вом функ­ции со стро­ко­вы­ми па­ра­мет­ра­ми и уве­ли­чим чис­ло про­хо­дов с 5 000 до 50 000. Для теста со стро­ка­ми за­ком­мен­ти­ру­ем стро­ки с чи­сло­вы­ми па­ра­мет­ра­ми функ­ции.

Ре­зуль­тат тести­ро­вания на мо­ём но­ут­бу­ке пред­став­лен на рис. 3.

Как ви­ди­те, да­же оп­ти­ми­зи­руя доста­точ­но про­стую, и, с пер­во­го взгля­да, гра­мот­но напи­сан­ную функ­цию, не имею­щую дли­тель­ных или вло­жен­ных цик­лов, мы до­би­лись от­но­си­тель­но непло­хих ре­зуль­та­тов, уско­рив её ра­бо­ту в це­лом бо­лее чем на 20 %, а в от­дель­ных слу­ча­ях – и бо­лее чем на 35 %. На прак­ти­ке же, осо­бен­но со слож­ны­ми функ­ция­ми, мож­но до­би­вать­ся го­раз­до бо­лее впе­чат­ляю­ще­го ре­зуль­та­та.

Об­щие прин­ци­пы

На­конец, да­вай­те рас­смот­рим неко­то­рые об­щие прин­ци­пы оп­ти­ми­за­ций при­менитель­но к StarBasic. Во-пер­вых, сле­ди­те за пе­ре­мен­ны­ми – ста­рай­тесь объ­яв­лять их яв­но. Опе­ра­ции с необъ­яв­ лен­ны­ми пе­ре­мен­ны­ми про­ис­хо­дят мед­леннее. В са­мо­кон­тро­ле хо­ро­шо помогает ин­ст­рук­ция Option Explicit, до­бав­лен­ная в на­ча­ло мо­ду­ля. Кон­тро­ли­руй­те ти­пы и из­бе­гай­те их пре­об­ра­зо­вания.

Ис­поль­зуй­те кон­стан­ты вме­сто пе­ре­мен­ных там, где это воз­мож­но. Кон­стан­ты бы­ст­рее пе­ре­мен­ных. Ис­поль­зуй­те бо­лее эко­ном­ные кон­ст­рук­ции цик­лов. StarBasic под­дер­жи­ва­ет кон­ст­рук­цию пе­ре­бо­ра всех эле­мен­тов мас­си­ва For Each, хо­тя она и не до­ку­мен­ти­ро­ва­на. Дан­ный цикл не ис­поль­зу­ет счёт­чик и по­то­му бы­ст­рее цик­ла For To Step. Син­так­сис кон­­ст­рук­ции та­ков:

For Each Var In Arr
	 …
Next

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

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

От­дель­ную те­му для раз­го­во­ра пред­став­ля­ют со­бой усло­вия. Са­ми по се­бе они пре­достав­ля­ют ог­ром­ный про­стор для оп­ти­ми­за­ции, а в от­но­шении рас­смат­ри­вае­мой реа­ли­за­ции Basic – тем бо­лее. Об осо­бен­но­сти, свя­зан­ной с про­вер­кой незна­ча­щих усло­вий, мы уже го­во­ри­ли вы­ше. А что мож­но сде­лать ещё?

Во-пер­вых, мож­но по­про­бо­вать от­ка­зать­ся от усло­вий во­об­ще, за­менив их чис­лен­ным ре­шением или вы­бо­ром из мас­си­ва. Про­стей­ший при­мер, из­вест­ный по шу­точ­но­му тесту, где зна­чение пе­ре­мен­ной необ­хо­ди­мо за­менить с 1 на 2 и на­обо­рот, да­ёт в чис­лен­ном ре­шении пя­ти­крат­ный рост про­из­во­ди­тель­но­сти, то есть код

Val = 3 – Val

в пять раз бы­ст­рее, чем

If Val = 1 Then
	 Val = 2
Else
	 Val = 1
End If

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

Пусть есть неслож­ная за­да­ча: за­менить чи­сло­вые зна­чения от 0 до 20 их стро­ко­вы­ми на­име­но­вания­ми. Тут ре­шение на­пра­ши­ва­ет­ся са­мо со­бой, и лю­бая про­вер­ка усло­вий ка­жет­ся лишней:

Public Function MyFunction (ByVal Optional iNum As Integer) As
String
	 Dim aNameNum() As String
	 aNameNum = Array(“ноль”,“один”,“два”,“три”,“че­ты­ре”,“пять”,“шесть”,“семь”,“во­семь”,“де­вять”,“де­сять”,
                         “оди­нна­дцать”,“две­на­дцать”,“три­на­дцать”,“че­тыр­на­дцать”,“пят­на­дцать”,“шест­на­дцать”,“сем­на­дцать”,“во­сем­на­дцать”,
                         “де­вят­на­дцать”,“два­дцать”)
	 MyFunction = aNameNum(iNum)
End Function

Од­на­ко ес­ли пе­ре­пи­сать эту функ­цию, ис­поль­зуя кон­ст­рук­цию Select Case

Public Function MyFunction (ByVal Optional iNum As Integer) As String
	 Dim aNameNum() As String
	 Select Case iNum
		 Case 0
			 MyFunction = “ноль”
		 …
		 …
		 …
		 Case 20
			 MyFunction = “два­дцать”
	 End Select
End Function

то в тесте мож­но об­на­ру­жить поч­ти дву­крат­ный при­рост про­из­во­ди­тель­но­сти. В чём же де­ло? По­че­му про­вер­ка 4‑5 усло­вий бы­ст­рее, чем од­но­крат­ное из­вле­чение из мас­си­ва?

Де­ло в том, что мы ис­поль­зу­ем ин­тер­пре­та­тор, а это зна­чит, что ка­ж­дая за­груз­ка мас­си­ва дан­ны­ми – это имен­но за­груз­ка: до вы­полнения функ­ции Array он не су­ще­ству­ет нигде в ви­де бло­ка дан­ных. А за­груз­ка мас­си­ва – опе­ра­ция доста­точ­но ре­сур­со­ём­кая.

Так что же, та­кое ре­шение во­об­ще не име­ет пра­ва на су­ще­ство­вание? От­нюдь. И оно мо­жет да­же ока­зать­ся наи­бо­лее бы­ст­рым из всех: клю­че­вое сло­во здесь – кэш.

Рис. 4. Рис. 4. Ус­ло­вие про­тив вы­бор­ки из мас­си­ва.

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

И на­конец

Я обе­щал вер­нут­ся к во­про­су о сти­ле про­грам­ми­ро­вания, пре­ду­смат­ри­ваю­щем ко­ди­ро­вание пу­тём исклю­чения [Coding by exception], то есть та­ком, когда до­бав­ля­ет­ся до­полнитель­ный код для ка­ж­до­го спе­ци­аль­но­го слу­чая.

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

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

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию