FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

GAP Benchmarkの各プログラムの構成について (sssp.cc)

GAP Benchmarkの各プログラムの中身を調べている。sssp.ccについて調べた。

sssp: Single source shortest path

いわゆる一般的なグラフ問題で、あるノード(1点:single)からすべてのノードに対する最短距離を検出するためのアルゴリズムを総じてSSSPと呼ぶ。 これのもっとも有名なものにダイクストラ法があり、発展形としてすべてのノードからの最短距離を検出するワーシャル・フロイド法が有名である。

GAPでは、SSSPのベンチマークとしてDelta Stepping法というものが使われている。 Delta Stepping法はダイクストラ法と似ているが、よりマルチスレッドでの動作に適しているという特徴がある。

./sssp -f benchmark/graphs/twitter.wsg -n64 -d2 > benchmark/out/sssp-twitter.out
./sssp -f benchmark/graphs/web.wsg -n64 -d2 > benchmark/out/sssp-web.out
./sssp -f benchmark/graphs/road.wsg -n64 -d50000 > benchmark/out/sssp-road.out
./sssp -f benchmark/graphs/kron.wsg -n64 -d2 > benchmark/out/sssp-kron.out
./sssp -f benchmark/graphs/urand.wsg -n64 -d2 > benchmark/out/sssp-urand.out

また、このSSSPはGraph 500のベンチマークとしても使用されているらしい。

news.mynavi.jp

Graph処理のベンチマークであるが、まずは巨大なグラフを作る必要があり、この部分がカーネル1である。そして、BFS(Breadth First Search)という処理がカーネル2、SSSP(Single Source Shortest Path)という処理がカーネル3と呼ばれている。

まずは、Delta Steppingの基本的なアルゴリズムについて。これはWikipediaとか、ChatGPTに聞きながらいろいろ勉強した。

バケットと呼ばれるデータ構造をもとにアルゴリズムが進んでいく。バケットは複数に分かれており、δステップ毎に区間が区切られており、その区間に所属するノードが入る仕組みになっている。 つまり、最大ノード間距離がLの場合、L/δ個のバケットが用意されることになる。

まず、各頂点vについて、始点sのノードからの最短距離d[v]を無限大に設定する状態からスタートする。 この時、最初のバケット(bucket[0])に、始点sを挿入する。

アルゴリズムが終了するまで以下の手順を繰り返します:

  1. 空でない最小のバケットBを見つける
  2. Bから頂点vを取り出し、vを「訪問済み」とする
  3. vから直接到達可能な全ての頂点uについて、以下のステップを実行する:
    1. 頂点uまでの新しい予想距離を計算する:d_new = d[v] + w(v, u)。ここで、w(v, u)は頂点vとuの間のエッジの重みである
    2. d_newがd[u]より小さい場合、d[u]をd_newに更新し、頂点uを対応するバケットに挿入する。この挿入は、uが既に別のバケットに存在している場合でも行う。
  4. 全ての頂点が訪問済みになったらアルゴリズムを終了する

以下の資料を見ながら、具体的に考えてみる。

cs.iupui.edu

以下では、わかりやすくするために各ステップでノードの名前と距離を表示している(PowerPointで編集するの疲れた...)

1. この例では、各バケットのδ値は3で、B[1]=[3, 6)を保持している。 中央のSourceノードはB[0]に格納されており、そこから接続されているノードに対して探索を行う。

2. ノードB, C, E(青色ノード)はそれぞれSourceノードから重み3、ノードDは重み5のため、B[1]に入れられる。

  • Bucket[0] =
  • Bucket[1] = B:3, C:3, D:5, E=3

3. 複数のスレッドがバケットからノードを取り出し処理を行う。そのノードから到達可能なノードで、エッジの重みがδ=3以下のものは接続先のノードがオレンジ、エッジの重みがδ=3よりも大きなエッジはノードの色が赤で表現されている。

バケットからすべてのノードが取り出され、Bucket[1]は空となる。しかし、取り出されたノードB, C, D, Eは距離が確定したわけではないので、黄色のノードとして表現されている。

  • Bucket[0] =
  • Bucket[1] =

黄色のノードから到達可能なノードは、そのエッジの重みによってBucket[1]もしくはBucket[2]に挿入される。

  • Bucket[0] =
  • Bucket[1](青) = G, I, O, P
  • Bucket[2](紫) = Q, R, H, F, M, L, N

4.Bucket[1]から再びノードを取り出し、同様にエッジを調査する。到達可能なノードがオレンジ色で表示されている。

5.新たに、AA, AH, AG, TがBucket[2]の距離なので格納される。同様に、K, JノードがBucket[2]に格納され、AI, AK, AJノードがBucket[3]に格納される。

  • Bucket[0] =
  • Bucket[1] =
  • Bucket[2](青) = H, F M, L, N, R, Q, AA, AH, AG, T, K, J
  • Bucket[3](紫) = AI, AK, AJ

6.ノードUにおいて、オレンジ色のエッジからは、距離が8でBucket[2]に格納されるノードとなる。一方で、赤いエッジからは距離が11でBucket[3]に格納すべきノードとなる。 したがって、ここの比較処理はアトミックに行う必要がある。最終的に、ノードUはBucket[2]に格納される。

ここから先は省略するが、上記のような流れでBucketを更新しながら距離を計算していく。 さて、次は実際のコードの読み解きに移ろう。