12

このルーチンは、数字でいっぱいの大きな csv ファイルを作成するために無数に呼び出されます。これを行うためのより効率的な方法はありますか?

    static std::string dbl2str(double d)
    {
        std::stringstream ss;
        ss << std::fixed << std::setprecision(10) << d;              //convert double to string w fixed notation, hi precision
        std::string s = ss.str();                                    //output to std::string
        s.erase(s.find_last_not_of('0') + 1, std::string::npos);     //remove trailing 000s    (123.1200 => 123.12,  123.000 => 123.)
        return (s[s.size()-1] == '.') ? s.substr(0, s.size()-1) : s; //remove dangling decimal (123. => 123)
    }
4

3 に答える 3

10

始める前に、この機能にかなりの時間が費やされているかどうかを確認してください。これを行うには、プロファイラーまたはその他の方法で測定します。あなたがそれを無数の回数と呼んでいることを知っていることはすべて非常に良いことですが、それでもあなたのプログラムがこの関数にその時間の1%しか費やしていないことがわかった場合、ここで何もしないとプログラムのパフォーマンスが1%以上向上する可能性があります。その場合、あなたの質問に対する答えは「あなたの目的のためにいいえ、この機能を大幅に効率化することはできず、試してみるとあなたの時間を無駄にしている」でしょう。

まず、避けてくださいs.substr(0, s.size()-1)。これにより文字列の大部分がコピーれ、関数がNRVOの対象外になるため、通常、戻り時にコピーを取得すると思います。したがって、最初に行う変更は、最後の行を次のように置き換えることです。

if(s[s.size()-1] == '.') {
    s.erase(s.end()-1);
}
return s;

ただし、パフォーマンスが深刻な問題である場合は、次のようにします。これが可能な限り最速であるとは約束していませんが、不要な割り当てやコピーに関するいくつかの問題を回避できます。関係するアプローチでstringstreamは、文字列ストリームから結果へのコピーが必要になるため、より低レベルの操作が必要snprintfです。

static std::string dbl2str(double d)
{
    size_t len = std::snprintf(0, 0, "%.10f", d);
    std::string s(len+1, 0);
    // technically non-portable, see below
    std::snprintf(&s[0], len+1, "%.10f", d);
    // remove nul terminator
    s.pop_back();
    // remove trailing zeros
    s.erase(s.find_last_not_of('0') + 1, std::string::npos);
    // remove trailing point
    if(s.back() == '.') {
        s.pop_back();
    }
    return s;
}

への2番目の呼び出しは、連続したストレージsnprintfを使用することを前提としています。std::stringこれはC++11で保証されています。std::stringこれはC++03では保証されていませんが、C++委員会に知られているアクティブに保守されているすべての実装に当てはまります。パフォーマンスが本当に重要な場合は、文字列に直接書き込むことで後で文字列にコピーする手間が省けるため、移植性のない仮定を行うのが合理的だと思います。

s.pop_back()はC++11の言い方s.erase(s.end()-1)でありs.back()s[s.size()-1]

別の可能な改善のために、への最初の呼び出しを取り除き、snprintf代わりに(基本的に、必要な長さ)のsような値にサイズを変更することができます。問題は、これにより、通常必要とされるよりもはるかに多くのメモリが割り当てられ、ゼロになることです(IEEEダブルの場合は322バイト)。私の直感では、これは最初の呼び出しよりも遅くなります。呼び出し元が文字列の戻り値をしばらくの間保持している場合は、メモリの浪費は言うまでもありません。しかし、いつでもテストできます。std::numeric_limits<double>::max_exponent10 + 14-DBL_MAXsnprintf

あるいは、std::max((int)std::log10(d), 0) + 14必要なサイズの適度に厳しい上限snprintfを計算し、正確に計算できるよりも速い場合があります。

最後に、関数インターフェースを変更することでパフォーマンスを向上させることができるかもしれません。たとえば、新しい文字列を返す代わりに、呼び出し元から渡された文字列に追加することができます。

void append_dbl2str(std::string &s, double d) {
    size_t len = std::snprintf(0, 0, "%.10f", d);
    size_t oldsize = s.size();
    s.resize(oldsize + len + 1);
    // technically non-portable
    std::snprintf(&s[oldsize], len+1, "%.10f", d);
    // remove nul terminator
    s.pop_back();
    // remove trailing zeros
    s.erase(s.find_last_not_of('0') + 1, std::string::npos);
    // remove trailing point
    if(s.back() == '.') {
        s.pop_back();
    }
}

次に、呼び出し元はreserve()十分なスペースを確保し、関数を数回呼び出し(おそらく、間に他の文字列を追加して)、結果のデータブロックを、以外のメモリ割り当てなしで、ファイルに一度に書き込みますreserve。「たっぷり」はファイル全体である必要はなく、一度に1行または「段落」にすることもできますが、無数のメモリ割り当てを回避することでパフォーマンスが向上する可能性があります。

于 2013-03-01T21:31:01.843 に答える
5

速度または簡潔さの点で効率的ですか?

char buf[64];
sprintf(buf, "%-.*G", 16, 1.0);
cout << buf << endl;

「1」を表示します。科学表記法に戻す前に、有効数字 16 桁までフォーマットし、末尾にゼロを付けません。

于 2013-12-22T12:04:24.093 に答える
1
  • and の代わりにandsnprintfの配列を使用charstringstreamstring
  • char出力先のdbl2str に buffer へのポインターを渡します(string戻るときに呼び出される のコピー コンストラクターを回避するため)。文字バッファーに出力する文字列を組み立てます (または、文字バッファーが呼び出されたときに文字バッファーを変換するか、既存の文字列に追加します)。
  • inlineヘッダー ファイルで関数を宣言する

    #include <cstdio>
    inline void dbl2str(char *buffer, int bufsize, double d)
    {
      /** the caller must make sure that there is enough memory allocated for buffer */
      int len = snprintf(buffer, bufsize, "%lf", d);
    
      /* len is the number of characters put into the buffer excluding the trailing \0
         so buffer[len] is the \0 and buffer[len-1] is the last 'visible' character */
    
      while (len >= 1 && buffer[len-1] == '0')
        --len;
    
      /* terminate the string where the last '0' character was or overwrite the existing
         0 if there was no '0' */
      buffer[len] = 0;
    
      /* check for a trailing decimal point */
      if (len >= 1 && buffer[len-1] == '.')
        buffer[len-1] = 0;
    }
    
于 2013-03-01T19:48:05.287 に答える