web-dev-qa-db-ja.com

c ++ 11可変個引数Printfのパフォーマンス

可変個引数テンプレートはc ++ 11で導入されました。そして、printf関数はそれを使用して置き換えることができることがわかりました。ただし、実装ではcoutが使用されます。型安全性を実現するために他の何かを使用することは可能であるが、パフォーマンスをあまり犠牲にしないことは可能かどうか疑問に思っています。

void safe_printf(const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw "invalid format string: missing arguments";
            }
        }
        std::cout << *s++;
    }
}

template<typename T, typename... Args>
void safe_printf(const char *s, T& value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;
                safe_printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }
    throw "extra arguments provided to printf";
}

編集:リクエストに応じてOK、coutとprintfのパフォーマンスを比較するテストを実行します。 gccバージョン4.8.1、x86_64-redhat-linux

#include <iostream>
#include <cstdio>
#include <ctime>
#include <string>
#include <stdint.h>
#include <sstream>

using namespace std;
int32_t val = 10;
string s = "avcccc";

void TestOne(){
    for(int i = 0; i < 1000000; i++){
        printf("This is the value: %d, %s\n", i, s.c_str());
    }
}

void TestTwo(){
    ios_base::sync_with_stdio(false);

    for(int i = 0; i < 1000000; i++){
        cout << "This is the value: " << i << ", " << s << "\n";
    }
}

int main( int argc, char * argcv[] ) 
{
    timespec start;
    clock_gettime(CLOCK_REALTIME, &start);

    TestOne();

   timespec end;
   clock_gettime(CLOCK_REALTIME, &end);

   timespec start2;
   clock_gettime(CLOCK_REALTIME, &start2);

   TestTwo();

   timespec end2;
   clock_gettime(CLOCK_REALTIME, &end2);

   int64_t d1 = ((end.tv_sec - start.tv_sec) * 1000000000) + (end.tv_nsec - start.tv_nsec);

   int64_t d2 = ((end2.tv_sec - start2.tv_sec) * 1000000000) + (end2.tv_nsec - start2.tv_nsec);

   cout << "printf:" << d1 << " cout:" << d2 <<  ", diff(prinf-cout) " << d1-d2 << endl;
}

Sync_with_stdioをオフにしないでください。

printf:242862382 cout:275197806, diff(prinf-cout) -32335424
printf:201481941 cout:226177849, diff(prinf-cout) -24695908
printf:244915215 cout:275071790, diff(prinf-cout) -30156575
printf:235603694 cout:286859058, diff(prinf-cout) -51255364
printf:230076992 cout:248738879, diff(prinf-cout) -18661887
printf:223023056 cout:246486805, diff(prinf-cout) -23463749
printf:229922272 cout:269340309, diff(prinf-cout) -39418037
printf:228955230 cout:270675387, diff(prinf-cout) -41720157
printf:228390291 cout:228521421, diff(prinf-cout) -131130
printf:248419954 cout:268399737, diff(prinf-cout) -19979783
printf:229476729 cout:254635928, diff(prinf-cout) -25159199
printf:229646751 cout:252797729, diff(prinf-cout) -23150978
printf:227093478 cout:255390754, diff(prinf-cout) -28297276
printf:199161604 cout:224850870, diff(prinf-cout) -25689266
printf:198763304 cout:225709890, diff(prinf-cout) -26946586
printf:200445025 cout:223221944, diff(prinf-cout) -22776919
printf:202760174 cout:232363225, diff(prinf-cout) -29603051
printf:201989909 cout:225384923, diff(prinf-cout) -23395014
printf:202773400 cout:221465068, diff(prinf-cout) -18691668
printf:199076506 cout:223602064, diff(prinf-cout) -24525558
printf:200153338 cout:228610432, diff(prinf-cout) -28457094
printf:202794950 cout:223272108, diff(prinf-cout) -20477158
printf:209928648 cout:232300225, diff(prinf-cout) -22371577
printf:209722611 cout:233044140, diff(prinf-cout) -23321529
printf:248807680 cout:264406580, diff(prinf-cout) -15598900
printf:242916731 cout:246345645, diff(prinf-cout) -3428914
printf:221375038 cout:252251979, diff(prinf-cout) -30876941
printf:232892684 cout:273617728, diff(prinf-cout) -40725044
printf:238734317 cout:294238303, diff(prinf-cout) -55503986
printf:243180284 cout:284243039, diff(prinf-cout) -41062755
printf:245863056 cout:270413734, diff(prinf-cout) -24550678
printf:229300100 cout:259492696, diff(prinf-cout) -30192596
printf:227563904 cout:247802177, diff(prinf-cout) -20238273
printf:206847682 cout:230084237, diff(prinf-cout) -23236555
printf:198457595 cout:227655859, diff(prinf-cout) -29198264
printf:200265832 cout:224559564, diff(prinf-cout) -24293732
printf:201723563 cout:221764167, diff(prinf-cout) -20040604
printf:203430800 cout:221937208, diff(prinf-cout) -18506408
printf:198619127 cout:222313750, diff(prinf-cout) -23694623
printf:197686981 cout:233131170, diff(prinf-cout) -35444189
printf:199909645 cout:222764002, diff(prinf-cout) -22854357
printf:198050669 cout:221853069, diff(prinf-cout) -23802400
printf:200002449 cout:222743999, diff(prinf-cout) -22741550
printf:198573481 cout:221842796, diff(prinf-cout) -23269315
printf:200349434 cout:221501830, diff(prinf-cout) -21152396
printf:202039344 cout:238001170, diff(prinf-cout) -35961826
printf:209269614 cout:223050036, diff(prinf-cout) -13780422
printf:202794419 cout:219673749, diff(prinf-cout) -16879330
printf:223558422 cout:248919110, diff(prinf-cout) -25360688
printf:204139028 cout:231116364, diff(prinf-cout) -26977336

sync_with_stdioをオフにします

printf:228312005 cout:203814832, diff(prinf-cout) 24497173
printf:197367087 cout:170933621, diff(prinf-cout) 26433466
printf:203728802 cout:177833838, diff(prinf-cout) 25894964
printf:205758316 cout:180175924, diff(prinf-cout) 25582392
printf:251267252 cout:218081227, diff(prinf-cout) 33186025
printf:232029455 cout:199609356, diff(prinf-cout) 32420099
printf:246392438 cout:217010908, diff(prinf-cout) 29381530
printf:241149944 cout:202337266, diff(prinf-cout) 38812678
printf:222466515 cout:195872690, diff(prinf-cout) 26593825
printf:221633699 cout:206460840, diff(prinf-cout) 15172859
printf:229958105 cout:200284936, diff(prinf-cout) 29673169
printf:227201946 cout:195657372, diff(prinf-cout) 31544574
printf:244297134 cout:219042472, diff(prinf-cout) 25254662
printf:251881002 cout:216385940, diff(prinf-cout) 35495062
printf:227101117 cout:205227849, diff(prinf-cout) 21873268
printf:203951925 cout:177981416, diff(prinf-cout) 25970509
printf:201715048 cout:178496585, diff(prinf-cout) 23218463
printf:197549273 cout:175482278, diff(prinf-cout) 22066995
printf:239984907 cout:203745670, diff(prinf-cout) 36239237
printf:205235507 cout:181894912, diff(prinf-cout) 23340595
printf:203233202 cout:178961277, diff(prinf-cout) 24271925
printf:206434353 cout:173567831, diff(prinf-cout) 32866522
printf:202660160 cout:176737473, diff(prinf-cout) 25922687
printf:201831315 cout:175735846, diff(prinf-cout) 26095469
printf:200164787 cout:175192017, diff(prinf-cout) 24972770
printf:202584304 cout:177037255, diff(prinf-cout) 25547049
printf:204783650 cout:177513872, diff(prinf-cout) 27269778
printf:241033670 cout:220023173, diff(prinf-cout) 21010497
printf:247117709 cout:189569108, diff(prinf-cout) 57548601
printf:248105314 cout:219065335, diff(prinf-cout) 29039979
printf:236633133 cout:212964896, diff(prinf-cout) 23668237
printf:223496747 cout:195898087, diff(prinf-cout) 27598660
printf:224936154 cout:199397373, diff(prinf-cout) 25538781
printf:234572132 cout:216805022, diff(prinf-cout) 17767110
printf:246738504 cout:216305235, diff(prinf-cout) 30433269
printf:250904282 cout:217312285, diff(prinf-cout) 33591997
printf:208721377 cout:181195577, diff(prinf-cout) 27525800
printf:236245042 cout:203088785, diff(prinf-cout) 33156257
printf:204058026 cout:179401451, diff(prinf-cout) 24656575
printf:201038295 cout:176858089, diff(prinf-cout) 24180206
printf:204590031 cout:186586920, diff(prinf-cout) 18003111
printf:200774025 cout:177748984, diff(prinf-cout) 23025041
printf:203636203 cout:179084720, diff(prinf-cout) 24551483
printf:202479901 cout:179167171, diff(prinf-cout) 23312730
printf:202329892 cout:180674842, diff(prinf-cout) 21655050
printf:201305734 cout:179149029, diff(prinf-cout) 22156705
printf:200157089 cout:178860913, diff(prinf-cout) 21296176
printf:202803518 cout:177353141, diff(prinf-cout) 25450377
printf:204768180 cout:175608340, diff(prinf-cout) 29159840
printf:237805444 cout:205725465, diff(prinf-cout) 32079979

したがって、以前にsync_with_stdioを聞いたことがないのに、sync_with_stdioをオフにすると、coutはパフォーマンスprintfを出力できます。オフにすることによる副作用があるかどうかはわかりません。

上記の可変個引数printfがテストに含まれています。

void TestThree(){
   ios_base::sync_with_stdio(false);

   for(int i = 0; i < 1000000; i++){
       safe_printf("This is the value: %d, %s\n", i, s.c_str());
   }
}

繰り返しになりますが、sync_with_stdioがオフになっていても、私にはよくわかりませんが、印刷はさらに高速です。

printf:204099118 cout:684881656, diff(prinf-cout) -480782538
printf:227662054 cout:758110546, diff(prinf-cout) -530448492
printf:221534729 cout:732899502, diff(prinf-cout) -511364773
printf:203223553 cout:669386010, diff(prinf-cout) -466162457
printf:199923091 cout:662735504, diff(prinf-cout) -462812413
printf:207555026 cout:676522292, diff(prinf-cout) -468967266
printf:204846108 cout:655571171, diff(prinf-cout) -450725063
printf:199512948 cout:660717898, diff(prinf-cout) -461204950
printf:204251614 cout:676688426, diff(prinf-cout) -472436812
printf:201601655 cout:677217080, diff(prinf-cout) -475615425
printf:200311160 cout:674022044, diff(prinf-cout) -473710884
printf:202562272 cout:658126890, diff(prinf-cout) -455564618
printf:203100080 cout:660348312, diff(prinf-cout) -457248232
printf:228058969 cout:743305386, diff(prinf-cout) -515246417
printf:223976399 cout:742205352, diff(prinf-cout) -518228953
printf:204651603 cout:681893639, diff(prinf-cout) -477242036
printf:222386690 cout:745000014, diff(prinf-cout) -522613324
printf:217610811 cout:790129902, diff(prinf-cout) -572519091
printf:224156133 cout:758849874, diff(prinf-cout) -534693741
printf:204485998 cout:660430561, diff(prinf-cout) -455944563
printf:201259591 cout:667987480, diff(prinf-cout) -466727889
printf:204473955 cout:658096409, diff(prinf-cout) -453622454
printf:201986282 cout:670503647, diff(prinf-cout) -468517365
printf:204846379 cout:687089511, diff(prinf-cout) -482243132
printf:204962342 cout:681630774, diff(prinf-cout) -476668432
printf:202546744 cout:662300255, diff(prinf-cout) -459753511
printf:220856194 cout:757583221, diff(prinf-cout) -536727027
printf:232926622 cout:837024111, diff(prinf-cout) -604097489
printf:234187491 cout:801665699, diff(prinf-cout) -567478208
printf:244214945 cout:787558503, diff(prinf-cout) -543343558
printf:220227724 cout:788444837, diff(prinf-cout) -568217113
printf:227440252 cout:754596137, diff(prinf-cout) -527155885
printf:238158449 cout:814421312, diff(prinf-cout) -576262863
printf:205372147 cout:676782112, diff(prinf-cout) -471409965
printf:197566880 cout:662635516, diff(prinf-cout) -465068636
printf:199640024 cout:726088581, diff(prinf-cout) -526448557
printf:201249659 cout:660196078, diff(prinf-cout) -458946419
printf:201578923 cout:676444130, diff(prinf-cout) -474865207
printf:201034436 cout:690638993, diff(prinf-cout) -489604557
printf:208951246 cout:682032486, diff(prinf-cout) -473081240
printf:199928991 cout:673437083, diff(prinf-cout) -473508092
printf:233538795 cout:810890687, diff(prinf-cout) -577351892
printf:243872766 cout:847098807, diff(prinf-cout) -603226041
printf:243341748 cout:769877622, diff(prinf-cout) -526535874
printf:230232172 cout:733867319, diff(prinf-cout) -503635147
printf:220371109 cout:709704232, diff(prinf-cout) -489333123
printf:251803876 cout:790624221, diff(prinf-cout) -538820345
printf:202999188 cout:910149931, diff(prinf-cout) -707150743
printf:201931656 cout:669276274, diff(prinf-cout) -467344618
printf:200511370 cout:677748018, diff(prinf-cout) -477236648
11
Michael D

AndreiAlexandrescuによるsafe_printf関数は非常に巧妙ですが、残念ながら深刻な制限があります。

  1. 各引数は2回処理されます。1回目は有効性を確認するため、2回目はprintfでフォーマットするためです。オーバーヘッドを回避するためにリリースモードでチェックを無効にすることができますが、これは安全性を著しく損ないます。

  2. 位置引数では機能しません。

それを改善する方法はいくつかあります。

  1. 引数の型が確立されたら、フォーマットを常にprintfに転送しないでください。たとえば、 このベンチマーク は、sprintfよりも最大6.7倍高速な整数フォーマットを実装できることを示しています。

  2. 位置引数を実装するには、引数をインデックスでアドレス指定する必要があるため、引数を配列に格納する必要があります。

これを行う方法の例を次に示します。

class Arg {
 private:
  enum Type { INT, DOUBLE };
  Type type;
  union {
    int int_value;
    double dbl_value;
  } u;
 public:
  Arg(int value) : type(INT) { u.int_value = value; }
  Arg(double value) : type(DOUBLE) { u.dbl_value = value; }
  // other types
};

void do_safe_printf(const char *format, const Arg *args, size_t num_args) {
  // here we can access arguments by index
}

template <typename... Args>
void safe_printf(const char *format, const Args&... args) {
  Arg arg_array[] = {args...};
  do_safe_printf(format, arg_array, sizeof...(Args));
}

位置引数のサポートとは別に、このアプローチでは、すべての作業が単一の関数do_safe_printfによって行われるのに対し、safe_printf関数テンプレートは引数を配列に配置するだけなので、コードの膨張も最小限に抑えられます。

これらおよびその他の改善は、 fmtライブラリ に実装されています。ベンチマークによると、 速度コンパイルされたコードサイズ の両方でネイティブのprintf実装に匹敵します

免責事項:私はこのライブラリの作者です。

17
vitaut

GoingNative2012で、Andrei Alexandrescu 実装を提供 可変個引数のsafe_printf()。彼は2段階のアプローチを使用しています。まず、フォーマット指定子を確認します。次に、渡される引数を正規化します。実装は、フォーマッターと引数をチェックしてprintf()に委任するため、std::coutは見えず、実行時のオーバーヘッドはほとんどありません(通常のコードでは例外パスを頻繁に使用しないでください)。

コードの概要:

template <typename... Ts>
int safe_printf(const char * f, const Ts&... ts) 
{
    check_printf(f, normalizeArg(ts)...);  // check format specifiers
    return printf(f, normalizeArg(ts)...); // output with clean arguments
}

void check_printf(const char * f) 
{
    // checking is O(N) in length of format specifiers
    for (; *f; ++f) {
        if (*f != ’%’ || *++f == ’%’) continue;
        throw Exc("Bad format");
    }
}

// variadic check_print(const char*, T...) omitted, see slides

template <class T>
typename enable_if<is_integral<T>::value, long>::type
normalizeArg(T arg) 
{ 
    return arg; 
}

// more overloads for float, T* and std::string omitted, see slides
6
TemplateRex