現在使ってるJsonライブラリの処理を早く出来ないかどうか検討しました。
目的
・現在サーバーAPIのレスポンスをパースするJSONライブラリが遅いと感じてるのでなんか出来ないかどうか検討したい
・もっと早いライブラリがあると思うけどライブラリを差し替えるとけっこうな作業になるので今のままでやってみたい
・本来であればJSONを無くしてgRPCみたいなものを使いたいけどこれも差し替える作業が大きいので現在のものを改善できれば良い
stringアロケーションを少なくしましょう
最初にクラスを開いてみるとstringのアロケーションが多いなーと思いました。stringがコンストラクタに渡されてパースしながらstring.substring()を利用してガンガン再帰呼び出しがされてしまいます。
list.Add(newJSONObject(str.Substring(start,end)));
jsonが深くなるほどこれの呼ぶ回数が多くなるのでもともとのstringよりメモリが結構膨らむよね。パースが遅いだけじゃなくてGCも多く走らせてしまいます。
もし次のオブジェクトがstringじゃなければstring化しなくていいのではないか?
1個ずつのcharをチェックして次のデータが true、false、null、数字などだったらsubstringのコールを飛ばしてみよう。
擬似コード:
privatestaticreadonlychar[]ms_digits=newchar[]{'0','1','2','3','4','5','6','7','8','9','.','-','e','E'};privateJSONObjectparseSubString(stringstr,intstart,intlength){//true, false, nullなどのタイプをチェックif(length==4&&(str[start]=='t'||str[start]=='T')){//truereturnnewJSONObject(true);}if(length==5&&(str[start]=='f'||str[start+1]=='F')){//falsereturnnewJSONObject(false);}if(length==4&&(str[start]=='n'||str[start+1]=='N')){//nullreturnnewJSONObject(Type.NULL);}boolisNumber=false;for(inti=0;i<length;i++){//ms_digitsに入ったらindex返すintnumIdx=GetNumberFromChar(nextChar);//数字じゃないならやめるif(numIdx<0){isNumber=false;break;}// hogehoge//1個ずつの数字を追加していく//num = (num*10)+numIdx 的な感じ// マイナス、少点数などの対応も忘れずに}//数字を返すif(isNumber)returnnewJSONObject(number);//結局stringかオブジェクトだったらsubstringしちゃうかーreturnnewJSONObject(str.Substring(start,length));}
これを実装したらsubstringを呼ぶ回数が減るはずです!
ちなみに同じロジックを使ってStringBuilderでも拡張できます!
Gavin Pughさんが作ってくれた http://www.gavpugh.com/source/StringBuilderExtNumeric.csを参考してください
さらに減らせるでしょうか
上記だとある程度stringアロケーション減らせたけどまだjsonオブジェクトや配列などがsubstringに入ってくる。最終的なstringオブジェクトのみアロケートするように出来るでしょうか?
現在のライブラリだとstringが受け取るconstructorしかないけど、これMemoryStreamだったらどうかな?
もともとの
publicJSONObject(stringstr)
コンストラクタじゃなくて新しく
publicJSONObject(Streamstream)
を作ってみました。もともとのパース処理を真似しながら
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倍じゃん!
やっぱり無駄なものを削りましょう