03/10 シェルソート高速化比較(さらに改良)
先日投稿していただいたshell sortに、若干の改良を加えてさらに高速化したAppleScriptです。
tsuki3さんから投稿していただいたshell sortのコメントに、
ちなみに私の経験上、リストの要素数を得る場合は “lenth of ~” を使うよりも “count ~” を用いた方が速いはずです。また、私のスクリプトでは 複数の要素を纏めて set しておりますが、 これを別個にすればより速くなるはずです。
とあったので、すべて試してみました。

MacBook Pro 2010 Core i7 2.66GHz, RAM 8GB, Mac OS X 10.6.8の環境で改良版の「shellSort5」を試してみたところ、shellSort4で5秒かかっていた3万件のソートが4秒に。正直、こんなに速くなるとは思ってもみませんでした。ループ回数が多いので、わずかなスピードアップの集積が大きいんでしょうね。
Classic Mac OSの時代には、サードパーティのOSAX(スクリプティング機能拡張)の機能を呼び出してハードウェア制御(キーボードLEDの点灯制御、シリアルポート経由の機器制御)を行ったり、高度な命令を利用することを目指していました。最終的には200個ぐらいのOSAXをシステムにインストールして使っていたほど。
ソートに関しても、サードパーティのOSAXで提供されるソート命令を利用することが多かったのですが、Mac OS Xの時代になってMac OS X対応できない(or しない)OSAXが多数あったため、OSAXへの依存度を下げることが必要になってきました。のちに、Universal Binary対応や64ビット対応など、さまざまなハードルがあってOSAX側の対応が後手後手に回る中、Mac OS Xへの移行当初からOSAXへの依存度を下げる努力をしてきたため、自分はあまり影響を受けませんでした。また、このOSAX依存度軽減の努力は、AppleScript StudioによるGUIベースのアプリケーション開発時にもおおいに役立ちました(アプリケーションと一緒に再配布できないライセンス条件のOSAXなどもあったため)。
正直なところ、ソートルーチンなんて一度作ってそこそこ満足できれば、見直すことなどほとんどない部品。最初のバージョンからここまでいろいろと高速化ができたのは、多くのScripterの方々の努力と経験によるところが大きく、1人でやっているだけでは、なかなか手が回らないところだと思います。
そういう意味では、こういうBlogを立ち上げて地道に活動してきた意義というのはあるのではないかと。この3月でスタートして4年が経過。掲載Scriptも1,000本を超えました。当初の、「サブルーチン資産に対するMac OS Xバージョンアップの影響検証」というねらいを超えて、技術的な交流の場として機能していけたらいいですね。
| スクリプト名:(数値ソート) シェルソート_v2.scpt |
| set lst to {} set lstr to (a reference to lst) repeat 30000 times set lstr’s end to (random number 999) end repeat set t to (current date) set res to my shellSort5(lst) – 正誤確認用 (*set resr to (a reference to res) repeat with i from 2 to (count resr) if (resr’s item (i - 1) > resr’s item i) then return false end repeat*) return {“” & ((current date) - t) & “秒”} on shellSort1(lst) – 配列を使用した基本形 set len to (count lst) set gap to 1 repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len - 1) set {temp, j} to {lst’s item (i + 1), i} repeat while ((j ≥ gap) and (lst’s item (j - gap + 1) > temp)) set {lst’s item (j + 1), j} to {lst’s item (j - gap + 1), j - gap} end repeat set lst’s item (j + 1) to temp end repeat end if end repeat return lst end shellSort1 on shellSort2(lst) – 参照 (a reference to…) を使用 set {lstr, len, gap} to {a reference to lst, count lst, 1} repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len - 1) set {temp, j} to {lstr’s item (i + 1), i} repeat while ((j ≥ gap) and (lstr’s item (j - gap + 1) > temp)) set {lstr’s item (j + 1), j} to {lstr’s item (j - gap + 1), j - gap} end repeat set lstr’s item (j + 1) to temp end repeat end if end repeat return lst end shellSort2 on shellSort3(lst) – レコードと参照、グローバル変数を併用 global rec set rec to {list:lst} set {recr, len, gap} to {a reference to rec, count lst, 1} repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len - 1) set {temp, j} to {recr’s list’s item (i + 1), i} repeat while ((j ≥ gap) and (recr’s list’s item (j - gap + 1) > temp)) set {recr’s list’s item (j + 1), j} to {recr’s list’s item (j - gap + 1), j - gap} end repeat set recr’s list’s item (j + 1) to temp end repeat end if end repeat return recr’s list end shellSort3 on shellSort4(lst) – スクリプトオブジェクトを使用 script o property list : lst end script set {len, gap} to {count o’s list’s items, 1} repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len - 1) set {temp, j} to {o’s list’s item (i + 1), i} repeat while ((j ≥ gap) and (o’s list’s item (j - gap + 1) > temp)) set {o’s list’s item (j + 1), j} to {o’s list’s item (j - gap + 1), j - gap} end repeat set o’s list’s item (j + 1) to temp end repeat end if end repeat return o’s list end shellSort4 –プレーンなAppleScriptベースでは最速ソート on shellSort5(aSortList) – スクリプトオブジェクトを使用 script oBj property list : aSortList end script set len to count oBj’s list’s items set gap to 1 repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len - 1) set temp to oBj’s list’s item (i + 1) set j to i repeat while ((j ≥ gap) and (oBj’s list’s item (j - gap + 1) > temp)) set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1) set j to j - gap end repeat set oBj’s list’s item (j + 1) to temp end repeat end if end repeat return oBj’s list end shellSort5 |

