メモの日々


2017年03月07日(火) [長年日記]

[c][c++] open_memstream()

glibcなどにはopen_memstreamという関数がある。

FILE *open_memstream(char **ptr, size_t *sizeloc);

これは自動的に伸長するバッファをバックエンドに持つFILEポインタを返してくれる。データ長が分からない文字列構築をするときなどに便利。なお、sizelocにNULLを指定することはできない模様。サンプルコードと実行結果をメモ。

#include <stdio.h>
#include <stdlib.h>

int main() {
  char* p;
  size_t size;

  FILE* fp = open_memstream(&p, &size);
  if (!fp) return 1;

  fputs("hello", fp);
  fflush(fp);
  printf("%s %lu\n", p, size);

  fputs("world", fp);
  fflush(fp);
  printf("%s %lu\n", p, size);

  // バッファの先頭から書き込みなおす
  rewind(fp);

  fputs("hi", fp);
  fclose(fp);
  printf("%s %lu\n", p, size);

  free(p); // 要free
  return 0;
}
hello 5
helloworld 10
hi 2

[c++] std::ostringstreamは遅い

ostringstreamが遅いことは以前メモした。これはマルチスレッド動作時のことだったけど、シングルスレッドでもfprintfなどより遅い。

それでもostringstreamにはバッファサイズの管理をしなくてよいという便利なところがある。しかし、stdio.hにも上述のopen_memstreamがあるので、これを使えばostringstreamよりも速い出力文字列ストリームを作れるはず。

以下にopen_memstreamを使ったmy_ostringstreamとstd::ostringstreamの比較プログラムをメモ。my_ostringstreamの方が少しだけ速い。my_ostringstreamは標準ライブラリのストリームに似せているだけでstd::ostreamではないことに注意。

#include <chrono>
#include <iostream>
#include <sstream>
#include <stdexcept>

#include <stdio.h>
#include <stdlib.h>

class my_ostringstream {
public:
  my_ostringstream() : fp_{open_memstream(&buffer_, &buffer_size_)} {
    if (!fp_) throw std::runtime_error{"open_memstream failed"};
  }

  // copyやmoveは面倒
  my_ostringstream(const my_ostringstream&) = delete;
  my_ostringstream& operator=(const my_ostringstream&) = delete;
  my_ostringstream(my_ostringstream&&) = delete;
  my_ostringstream& operator=(my_ostringstream&&) = delete;

  ~my_ostringstream() {
    std::fclose(fp_);
    std::free(buffer_);
  }

  std::string str() {
    std::fflush(fp_);
    return {buffer_};
  }

  explicit operator bool() const { return !is_failed_; }

  my_ostringstream& operator<<(char c) {
    if (std::fputc(c, fp_) == EOF) is_failed_ = true;
    return *this;
  }

  my_ostringstream& operator<<(double d) {
    if (std::fprintf(fp_, "%g", d) < 0) is_failed_ = true;
    return *this;
  }

private:
  char* buffer_;
  std::size_t buffer_size_;
  FILE* fp_;
  bool is_failed_{false};
};

template<typename Out>
void call(const std::string& name, Out&& out) {
  using namespace std::chrono;
  constexpr int n = 1000000;

  const auto s = system_clock::now();
  for (double d = 0; d <= n; d += 0.1) {
    out << d << ' ';
  }
  const auto e = system_clock::now();

  std::cout
      << out.str().size()
      << ", " << duration_cast<milliseconds>(e - s).count() << " ms"
      << ", " << name
      << std::endl;
}

int main() {
  call("my_ostringstream", my_ostringstream{});
  call("std::ostringstream", std::ostringstream{});
}
70688902, 6103 ms, my_ostringstream
70688902, 7639 ms, std::ostringstream