文字列を数値に変換

先日、私の知り合いの一人がプログラミングを理解するのを助けました。 その過程で、彼らは文字列を数値(int)に変換できるトレーニングプログラムを作成しました。 そして、どういうわけか私は自分の不滅の速度を標準ツール(Convert.ToInt32とInt32.Parse)の速度と比較したかった。 この比較の結果は、一見したところ、やや珍しいものでした。



誰でも問題なく問題を解決できると思うので、それが何をどのように機能するかを説明するポイントがわかりません。

class MyConvert { private static int CharToInt(char c) { return c - '0'; } public static int ToInt(string s) { if (s == null) throw new ArgumentException(); if (s.Length == 0) throw new ArgumentException(); bool isNegative = false; int start = 0; switch (s[0]) { case '-': if (s.Length == 1) throw new ArgumentException(); start = 1; isNegative = true; break; case '+': if (s.Length == 1) throw new ArgumentException(); start = 1; break; } int result = 0; for (int i = start; i < s.Length; i++) { if (c < '0' || c > '9') throw new ArgumentException(); result = checked(result * 10 + CharToInt(s[i])); } return (isNegative) ? -result : result; } }
      
      







以下の機能の速度を比較します。

  static void MyConvertTest(int numbersCount) { for (int i = - numbersCount; i < numbersCount; i++) { if (i != MyConvert.ToInt(i.ToString())) { throw new ArgumentException(); } } } static void ConvertTest(int numbersCount) { for (int i = - numbersCount; i < numbersCount; i++) { if (i != Int32.Parse(i.ToString())) { throw new ArgumentException(); } } }
      
      







numbersCount = 16,000,000のマシン(Windows 7、.NET 4.0、intel i5)では、平均して次の結果が得られます。

ConvertTest:08.5859994秒

MyConvertTest:07.0505985秒



Int32.Parseの代わりにConvert.ToInt32を使用すると、結果は変わりません。 しかし、Convert.ToInt32関数自体がInt32.Parseを呼び出すことを考えると、これは理解できます。 したがって、自分の自転車の速度は標準機能の速度よりも18%速いことがわかります。



ドキュメントを見ると、関数Int32.Parseが非常に複雑であることが明らかになります。 特に、地域の形式を考慮して、数値の文字列表現を変換できます。 私の練習では、この機能を使用する必要はありませんでした。



作成をさらに少し速くしようとしましょう。 これを行うには、ToInt関数のループを変更します

  for (int i = start; i < s.Length; i++)
      
      







  int length = s.Length; for (int i = start; i < length; i++)
      
      







この場合、以下を取得します。

MyConvertTest:06.2629928秒

つまり、関数の速度は標準よりも約27%速くなりました。 これはかなり予想外です。 コンパイラー(またはCLR)は、ループ内でs変数を変更しないため、s.Length値は一度しか受け取られないことを理解できると考えました。



次に、CharToInt関数を呼び出す代わりに、ToInt関数にその本体を埋め込みます。 この場合

MyConvertTest:05.5496214秒

したがって、標準機能に比べて動作速度は約35%増加しました。 コンパイラ(またはCLR)が独自にこれを実行できる場合があるため、これはかなり予想外です。



ほとんどすべてが試されました。 forループを放棄しようとするだけです。 これは、たとえば次のように実行できます。

  unsafe public static int ToInt(string s) { if (s == null) { throw new ArgumentException(); } int result = 0; bool isNegative = false; fixed(char* p = s) { char* chPtr = p; char ch = *(chPtr++); switch (ch) { case '-': isNegative = true; ch = *(chPtr++); break; case '+': ch = *(chPtr++); break; } do { if (ch < '0' || ch > '9') { throw new ArgumentException(); } result = result * 10 + (ch - '0'); ch = *(chPtr++); }while (ch != '\0'); } return (isNegative) ? -result : result; }
      
      





結果:

MyConvertTest:05.2410683秒

これは、標準機能よりも〜39%高速です(forオプションよりも3%高速です)。



結論



もちろん、文字列を数値に変換する関数の速度を上げようとしても、特定の値はありません。 このようなパフォーマンスの向上が少なくとも大きな影響を与える単一の状況を想像することはできません。 それにもかかわらず、いくつかの非常に明白な結論を下すことができます。





多くの場合、さまざまな文献で、すべての低レベルの最適化はコンパイラとランタイムの肩に置かれるべきであるというかなり公平な声明があります。 彼らは、彼らは非常に知的であり、彼ら自身がそれがどのように良くなるかをよく知っていると言う。 ただし、このようなテストが示すように、サイクルの最適化と機能の展開により、速度が20%向上する可能性があります(1番目のオプションを比較した場合)。



もちろん、最適化を賢明に行う必要があります。 そして、実際のプロジェクトでは常にそうではありませんが、プロジェクトサポートの複雑さは、作業を数秒加速することで正当化できます。



UPDはプラスの問題を修正しました、ありがとう。

UPD 2 it4_kpは、私が提案したすべてのアルゴリズムで機能しないことを正しく認識しました

 MyConvert.ToInt( int.MinValue.ToString() )
      
      





実際、int.MinValueはint.MaxValueよりも1多いモジュロです。 また、中間計算ではintからの正の範囲がバッファーとして使用されるため、int.MinValueからのモジュールは含まれません。

考えられる解決策の1つ:負の範囲を中間バッファーとして使用する

  unsafe public static int ToInt(string s) { .... result = result * 10 - (ch - '0'); ..... return (isNegative) ? result : checked(-result); }
      
      







一見原始的なコードにはすでに2つのエラーがあります。 標準ライブラリを放棄する前に考える追加の理由。



All Articles