MapReduce 型の並列処理によるコードクローン検出アルゴリズムと試験的な実装

MapReduce 型の並列処理によるコードクローン検出アルゴリズムと試験的な実装

MapReduce 型の並列処理を取り入れた,大規模なコードベースに適用可能なスケーラビリティを持つタイプ 3 コードクローン(重複コード)検出手法を提案する.手法を実現するツールを, 4 億行規模のコードベースに対して適用する実験を行った.実験には 1 台の計算機(ただしメモリ 128GiB)を利用し,数時間程度でコードクローンを検出した.

C5f2decbd0f13479c174297d587dc175?s=128

Toshihiro Kamiya

January 14, 2020
Tweet

Transcript

  1. 2.

    2 コードクローンとは • ソースコード中の重複したコード断片をコードクローンと呼ぶ – 典型的には、開発者がソースコードをコピペすることで作られる • コードクローンによりソースコード修正の手間が増大する(と言われて いる) –

    コードクローンにバグが見つかる→全部修正する必要 修正したつもりが修正できていない – 大規模なソースコードだとコードクローンを把握することすら難しい
  2. 3.

    3 コードクローン検出手法 • コードクローンを(特に大規模なソースコードから)検出するための手法 – 精度: コード断片がコピペされたあと修正されるような状況も検出できるか – スケーラビリティ: どのくらいの規模のソースコードに(実用的な処理時間

    で)適用できるか • 検出手法が利用するデータ表現・アルゴリズムの違い – トークンの列、AST, PDG, etc – N-gram, 接尾辞配列、同型グラフマイニング 、等 • コードクローン検出手法の応用 – リファクタリング、ソースコード修正前のチェック、 ソースコードの状態の可視化、等
  3. 4.

    4 コードクローンのタイプ コードクローン検出手法を分類するために提唱された タイプ 1 ソースコード中の空白文字やインデントの違いを除けば同 じ文字列となるコード断片→コピペしただけ タイプ 2 タイプ

    1 での変更に加えて、識別子(変数や関数の名前)を 変更したコード断片→コピペして変数や関数名がぶつからないよう に修正 タイプ 3 タイプ 1 および 2 での変更に加えて、ある程度の挿入や削 除が行われたコード断片→コピペ後に修正 タイプ 4 何らかの意味論を与えられたときに、その意味論で等価で あるとみなされるコード断片
  4. 6.

    6 インデックス MapReduce型並列処理 • もともとGoogleで大量のwebページからインデックスを作成するた めに利用された並列処理手法 • 大規模なデータをマップ、シャッフル、リデュースという3種類の操 作の組み合わせにより、計算機クラスタ上で分散処理 •

    Apache Spark等のフレームワークが利用可能 ページに 含まれる 単語とURLの タプルを作成 (単語,URL) 単語からそれを 含むページの リスト求める インデックスを 作成 (w, u) (w, u) (w, u) (w, u) (w, u) (単語, [URL, ...]) 単語で分類 ページのデータ を分配
  5. 7.

    7 検出手法 コード断片をトークンの集合として表現 → コードクローン検出 頻出アイテム ≒ 集合の特定 検出手法のステップ ステップ

    (1) 手続きの定義であるコー ド断片からトークンを要素とするアイ テム集合を求める ステップ (2) 共通の要素を持つアイテ ム集合の部分集合(クローンセット)を 特定 ステップ (3) 各クローンセットについ て、トークンの分布による後処理 int max(int a, int b) { if (a >= b) return a; else return b; } $P:{ $P:} $P:( $P:) $P:; $K:else $K:if $K:return $O:> $O:= $I:a $I:b トークンの集合として表現
  6. 8.

    8 マップ#1: コード断片f = { t1, t2, … }から、各トークンをキー、それを含むコード断片f を値とするタプル(t1,

    f), (t2, f), ...を作成 シャッフル#1: トークンによりタプルを分類 リデュース#1: トークンtを含むコード断片の集合F = {f1, f2, … }を求め、Fをキー、トー クンtを値とするタプル(F, t)を作成 シャッフル#2: Fによりタプルを分類 リデュース#2: F = {f1, f2, … }をクローンセットとする • 類似部分として、クローンセットのコード断片が共有するトークンも求める ステップ2のアルゴリズム {ab} {ac} (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {ab}) (a, {ac}) ({{ab}{ac}}, a) ({{ab}{ac}}, a) s: {a} f: {ab}, {ac} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 この例では、タプルの値が共有するトークンになっているが一般には異なる
  7. 9.

    9 マップ#1: コード断片f = { t1, t2, … }から、各トークンをキー、それを含むコード断片f を値とするタプル(t1,

    f), (t2, f), ...を作成 シャッフル#1: トークンによりタプルを分類 リデュース#1: トークンtを含むコード断片の集合F = {f1, f2, … }を求め、Fをキー、トー クンtを値とするタプル(F, t)を作成 シャッフル#2: Fによりタプルを分類 リデュース#2: F = {f1, f2, … }をクローンセットとする • 類似部分として、クローンセットのコード断片が共有するトークンも求める ステップ2のアルゴリズム {ab} {ac} (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {ab}) (a, {ac}) ({{ab}{ac}}, a) ({{ab}{ac}}, a) s: {a} f: {ab}, {ac} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 この例では、タプルの値が共有するトークンになっているが一般には異なる
  8. 10.

    10 マップ#1: コード断片f = { t1, t2, … }から、各トークンをキー、それを含むコード断片f を値とするタプル(t1,

    f), (t2, f), ...を作成 シャッフル#1: トークンによりタプルを分類 リデュース#1: トークンtを含むコード断片の集合F = {f1, f2, … }を求め、Fをキー、トー クンtを値とするタプル(F, t)を作成 シャッフル#2: Fによりタプルを分類 リデュース#2: F = {f1, f2, … }をクローンセットとする • 類似部分として、クローンセットのコード断片が共有するトークンも求める ステップ2のアルゴリズム {ab} {ac} (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {ab}) (a, {ac}) ({{ab}{ac}}, a) ({{ab}{ac}}, a) s: {a} f: {ab}, {ac} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 この例では、タプルの値が共有するトークンになっているが一般には異なる
  9. 11.

    11 マップ#1: コード断片f = { t1, t2, … }から、各トークンをキー、それを含むコード断片f を値とするタプル(t1,

    f), (t2, f), ...を作成 シャッフル#1: トークンによりタプルを分類 リデュース#1: トークンtを含むコード断片の集合F = {f1, f2, … }を求め、Fをキー、トー クンtを値とするタプル(F, t)を作成 シャッフル#2: Fによりタプルを分類 リデュース#2: F = {f1, f2, … }をクローンセットとする • 類似部分として、クローンセットのコード断片が共有するトークンも求める ステップ2のアルゴリズム {ab} {ac} (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {ab}) (a, {ac}) ({{ab}{ac}}, a) ({{ab}{ac}}, a) s: {a} f: {ab}, {ac} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 この例では、タプルの値が共有するトークンになっているが一般には異なる
  10. 12.

    12 マップ#1: コード断片f = { t1, t2, … }から、各トークンをキー、それを含むコード断片f を値とするタプル(t1,

    f), (t2, f), ...を作成 シャッフル#1: トークンによりタプルを分類 リデュース#1: トークンtを含むコード断片の集合F = {f1, f2, … }を求め、Fをキー、トー クンtを値とするタプル(F, t)を作成 シャッフル#2: Fによりタプルを分類 リデュース#2: F = {f1, f2, … }をクローンセットとする • 類似部分として、クローンセットのコード断片が共有するトークンも求める ステップ2のアルゴリズム {ab} {ac} (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {ab}) (a, {ac}) ({{ab}{ac}}, a) ({{ab}{ac}}, a) s: {a} f: {ab}, {ac} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 この例では、タプルの値が共有するトークンになっているが一般には異なる
  11. 13.

    13 マップ#1: コード断片f = { t1, t2, … }から、各トークンをキー、それを含むコード断片f を値とするタプル(t1,

    f), (t2, f), ...を作成 シャッフル#1: トークンによりタプルを分類 リデュース#1: トークンtを含むコード断片の集合F = {f1, f2, … }を求め、Fをキー、トー クンtを値とするタプル(F, t)を作成 シャッフル#2: Fによりタプルを分類 リデュース#2: F = {f1, f2, … }をクローンセットとする • 類似部分として、クローンセットのコード断片が共有するトークンも求める ステップ2のアルゴリズム {ab} {ac} (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {ab}) (a, {ac}) ({{ab}{ac}}, a) ({{ab}{ac}}, a) s: {a} f: {ab}, {ac} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 この例では、タプルの値が共有するトークンになっているが一般には異なる
  12. 14.

    14 少し大きな例 {ab} {ac} {abc} {ef} (a, {ab}) (b, {ab})

    (a, {ac}) (c, {ac}) (a, {abc}) (b, {abc}) (c, {abc}) (e, {ef}) (f, {ef}) (a, {ab}) (b, {ab}) (a, {ac}) (c, {ac}) (a, {abc}) (b, {abc}) (c, {abc}) (e, {ef}) (f, {ef}) ({{ab}{abc}{ac}}, a) ({{ab}{abc}}, b) ({{abc}{ac}}, c) ({{ef}}, e) ({{ef}}, f) ({{ab}{abc}{ac}}, a) ({{ab}{abc}}, b) ({{abc}{ac}}, c) ({{ef}}, e) ({{ef}}, f) s: {a} f: {ab}, {abc}, {ac} s: {ab} f: {ab}, {abc} s: {ac} f: {abc}, {ac} s: {ef} f: {ef} マップ#1 シャッフル#1 リデュース#1 シャッフル#2 リデュース#2 リデュース#2でコード 断片が共有するトーク ンがタプルの値と異な る例 リデュース#2で2つ以上の タプルがマージされる例
  13. 15.

    15 中間データが膨れ上がるケースと対策 • 極端に「長い」コード断片 – トークンの種類の2乗に 比例する中間データが 生成される – →

    「長い」コード断片 は除去する • 「頻出」トークン – → ストップワード扱い (t 1 , {t 1 t 2 ... t N }) マップ#1 {t 1 t 2 ... t N } (t 2 , {t 1 t 2 ... t N }) (t N , {t 1 t 2 ... t N }) ... N N2 最初の実験では、 中間データが膨れ上がり、 マップ#1・リデュース#1で処理 が止まった → 対策を講じた
  14. 17.

    17 実験: 検出パラメーター • コード断片の最大サイズ: 60 – 「長い」コード断片対策 • トークンの最大頻度:

    1000 – ストップワード • 類似部分の最小サイズ: 5 • ギャップの割合の最大: 0.3 – クローンセットの各コード断片のトークン列中から、共有されるトークン をすべて含み、かつ、それ以外のトークンの個数が最小の部分列を探す – その部分列が含む「それ以外」のトークンの割合がこの値より多ければ、 検出結果からそのコード断片を取り除く
  15. 20.

    20 まとめと課題 まとめ • 大規模なソースコードに適用可能な、タイプ3コードクローン検出手 法を提案 • 実装したツールを4億行のソースコードに適用 課題 •

    テストコードや自動生成コード等を除外して実験し直す • 実装上の都合で候補から取り除いた「長い」コード断片を扱えるよ うに工夫する • ベンチマークなどにより他の手法と比較する