現在使ってるJsonライブラリの処理を早く出来ないかどうか検討しました。
目的
・現在サーバーAPIのレスポンスをパースするJSONライブラリが遅いと感じてるのでなんか出来ないかどうか検討したい
・もっと早いライブラリがあると思うけどライブラリを差し替えるとけっこうな作業になるので今のままでやってみたい
・本来であればJSONを無くしてgRPCみたいなものを使いたいけどこれも差し替える作業が大きいので現在のものを改善できれば良い
stringアロケーションを少なくしましょう
最初にクラスを開いてみるとstringのアロケーションが多いなーと思いました。stringがコンストラクタに渡されてパースしながらstring.substring()を利用してガンガン再帰呼び出しがされてしまいます。
list.Add(new JSONObject(str.Substring(start, end)));
jsonが深くなるほどこれの呼ぶ回数が多くなるのでもともとのstringよりメモリが結構膨らむよね。パースが遅いだけじゃなくてGCも多く走らせてしまいます。
もし次のオブジェクトがstringじゃなければstring化しなくていいのではないか?
1個ずつのcharをチェックして次のデータが true、false、null、数字などだったらsubstringのコールを飛ばしてみよう。
擬似コード:
private static readonly char[] ms_digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', 'e', 'E' };
private JSONObject parseSubString(string str, int start, int length){
//true, false, nullなどのタイプをチェック
if(length == 4 && (str[start]=='t' || str[start]=='T')){
//true
return new JSONObject(true);
}
if(length == 5 && (str[start]=='f' || str[start+1]=='F')){
//false
return new JSONObject(false);
}
if(length == 4 && (str[start]=='n' || str[start+1]=='N')){
//null
return new JSONObject(Type.NULL);
}
bool isNumber = false;
for(int i=0;i<length;i++){
//ms_digitsに入ったらindex返す
int numIdx = GetNumberFromChar(nextChar);
//数字じゃないならやめる
if(numIdx < 0){ isNumber = false; break;}
// hogehoge
//1個ずつの数字を追加していく
//num = (num*10)+numIdx 的な感じ
// マイナス、少点数などの対応も忘れずに
}
//数字を返す
if(isNumber)
return new JSONObject(number);
//結局stringかオブジェクトだったらsubstringしちゃうかー
return new JSONObject(str.Substring(start, length));
}
これを実装したらsubstringを呼ぶ回数が減るはずです!
ちなみに同じロジックを使ってStringBuilderでも拡張できます!
Gavin Pughさんが作ってくれた http://www.gavpugh.com/source/StringBuilderExtNumeric.cs を参考してください
さらに減らせるでしょうか
上記だとある程度stringアロケーション減らせたけどまだjsonオブジェクトや配列などがsubstringに入ってくる。最終的なstringオブジェクトのみアロケートするように出来るでしょうか?
現在のライブラリだとstringが受け取るconstructorしかないけど、これMemoryStreamだったらどうかな?
もともとの
public JSONObject(string str)
コンストラクタじゃなくて新しく
public JSONObject(Stream stream)
を作ってみました。もともとのパース処理を真似しながら
stream.Read(buffer,0,1)
1個ずつのcharをチェックしながらjsonパースしていく。 こうするとアロケーションが発生する時はjsonの中のstringタイプのオブジェクトがある時だけになります。
では、このMemoryStreamがどこから来るでしょうか? stringをMemoryStreamに変換するだけだと意味がなくなります。HTTP通信周りのシステムを見てみると内部にMemoryStreamを使ってます! 今までは通信が終わったらこのMemoryStreamがbyte[]に変換されてたけどそれを飛ばして直接にJsonに渡したらさらに快適。けっこう大きい通信もあるのでこのbyte[]変換が必要ないならGCなども改善されます。
結果
こっちのテスト環境で試してみたけどもともと1.25秒かかったものが0.25秒になりました! 5倍じゃん!
やっぱり無駄なものを削りましょう