Saturday, January 7, 2012

Substitute for sprintf

snprintf/sprintf are widely used in our code. In one program, it takes up >30% cpu usage from kcachegrind report. It is necessary to rewrite sprintf for performance concern. In stead of sprintf, I prefer C++ stream style. Such a simple implementation as follows is almost 3X faster.

typedef std::pair<const char*, size_t> StrN;
typedef std::pair<size_t, size_t> NN; 
template <size_t L>
class StrStream
{
public:
  StrStream() : p_(buf_) {}
  size_t length() const { return p_ - buf_; }
  const char* get() const { return buf_; }
  void reverse(char* a, char* b)
  {
    while (a < b) std::swap(*(a++), *(b--));
  }
  template <typename T> StrStream& operator<<(T n)
  {
    assert(n>=0);
    char* p = p_; 
    do {
      *(p_++) = n % 10 + '0';
    } while (n /= 10);
    reverse(p, p_-1);
    return *this;
  }
  StrStream& operator<<(char c)  
  {
    *(p_++) = c;
    return *this;
  }
  StrStream& operator<<(StrN p)
  {
    while (p.second-- > 0) { // no *str check
      *(p_++) = *(p.first++);
    }
    return *this;
  }
  StrStream& operator<<(const char* str)
  {
    while (*str) {
      *(p_++) = *(str++);
    }
    return *this;
  }
  StrStream& operator<<(char* str) { return operator<<((const char*)str); }
  StrStream& operator<<(NN n)
  {
    assert(n.first>=0);
    char* p = p_;
    do {
      *(p_++) = n.first % 10 + '0';
    } while (n.first /= 10);
    char* p2 = p + n.second;
    while (p_ < p2) *(p_++) = '0';
    reverse(p, p_-1);
    return *this;
  }

private:
  char buf_[L];
  char* p_;
};

int main() {
  clock_t t = clock();
  for (size_t i = 0; i < 10000000; ++i) {
    char buf[256];
    sprintf(buf, "%ld %04ld %04ld %.4s", i, i, i, "abcde");
  }
  std::cout << (clock() -t) << std::endl;
  t = clock();
  for (size_t i = 0; i < 10000000; ++i) {
    StrStream<256> s;
    s << i << ' ' << NN(i, 4) << ' ' << NN(i, 4) << ' ' << StrN("abcde", 4);
  }
  std::cout << (clock() -t) << std::endl;
  return 0;
}

13830000
3560000

No comments:

Post a Comment