Where is the mate to the Sock?


November 19, 2019

Publishing Rust module in open source at crate.io

I published the rust module (crate) spin-sync.

spin-sync behaves like std::sync except for spin-sync bases on spinlock (Wikipedia Spinlock.)

I hope you will download this package. Thank you!!

Why do I need a spinlock?

To reduce the size.

For example, each instance of std::sync::Mutex takes 40 bytes additionally in case of my computer (x86_64 Linux.) On the other hand, a spinlock usually requires only 1 extra byte. (So does spin-sync.)

Generally speaking, it is not a good idea to share one mutex among many objects; the more sharing objects, the more lock competitions. However, too many mutexes could request a lot of memory. The size of mutex itself is important in such cases.

Imagine implementing a database, for example.

Assume that it has an internal cache system and that the average size of each cached element is 160 bytes. If each cached element has one mutex, how much memory the mutex instances consume totally?

If each mutex takes 40 bytes, the summary goes to as much as 20 % of the memory for the cache. However, if it takes only 1 byte, the consumption is less than 1 %.

Why did I create a new crate package?

Similar crates have already been published as open source. It is true. spin is one of them.

It is famous and many programmers have been using it. However, it seems dangerous a little to me.

The interface of spin is different from that of std::sync

Unlikely std::sync, spin doesn’t implement a strategy called “poisoning”, which propagates panics among threads.

I don’t think it makes a big matter. Poisoning rarely helps to find a bug.

The problem is the interface difference.

For example, method std::sync::Mutex::lock(&self) returns a Result wrapping the lock guard; because it should be an error if the mutex has been poisoned. However, spin::Mutex::lock(&self) returns the lock guard itself.

In other words, it is not very easy to refactor code basing on std::sync into what using spin.

I hope if spin::Mutex::lock(&self) would return a Result. It is a tiny problem never to return a Result never to be an error.

Sometimes, it is not easy to test synchronous processing. I don’t like to refactor code basing on spin because I can’t refer to code using std::sync.

(spin-sync implements the “poisoning” feature and spin-sync::Mutex::lock(&self) returns Result.)

spin doesn’t implement trait !Sync for the guards

This is a serious problem because it can bring the rust safeguard to ruin.

For example, spin::MutexGuard implements Sync, while std::sync::MutexGuard implements !Sync.

Sync has the opposite meaning from !Sync, of course.

I don’t know the reason; possibly, because nightly 1 toolchain 2 is required. Rust sometimes deduces and implements Sync trait automatically. (So does spin.) The programmer has to implement the negative trait 3 to stop the deduction, and nightly toolchain is necessary to build such code except for the std library.

Thanks to !Sync, rust compiler can detect a potential bug. spin disables the feature. It could be difficult for programmers realize as such.

(spin-sync implements !Sync trait. Nightly build is necessary so far, unfortunately.)

1

Rust has 3 release channels; “stable”, “beta” and “nightly”. Nightly is the most unstable of the 3.

2

a set of tools to compile a rust program.

3

A negative trait is a trait starting with ‘!’.

How about the performance?

Generally speaking, the performance of a spinlock is better than that of a mutex. So does spin-sync.

Compared to spin, spin-sync could be slower because of the poisoning implementation. However, the difference is little, if any. I don’t think it makes a serious problem.


November 15, 2019

GitHub Pages に blog 移行

今まで blog は WordPress で運営していたわけだが、この度 GitHub Pages に移行した。

過去記事を全部 reStructuredText に手動コンバートして、sphinx で html をジェネレート。

sphinx のデフォルト設定では、なぜか css が公開されなかった。 なので conf.pyextensions‘sphinx.ext.githubpages’ を追加。 すると上手く動いた(ように見える)

原因究明まではしてないけど、まあいいや。 _ で始まるディレクトリを非公開にする仕様とか、有ってもおかしくは無いし。

それにしても、blog 書くの 2 年半ぶりか。

更新頻度が少なすぎて、blog 公開方法の変更記事が全体のかなりの割合を占めているきがするw

あとで Twitter とかも js で埋め込んでみるかな。 時間が有ったら。


July 03, 2017

[c++] malloc したメモリを初期化する

この記事は、IT エンジニア向けです。

滅多に使わないので忘れるから、個人メモ。

c 言語ではメモリを確保する時に malloc を使う。 c++ では、同様に malloc を使う事も出来るが、普通は new を使う。

malloc はメモリを確保するだけだが、new はメモリ確保の後、確保したメモリの初期化まで行ってくれるからだ。

では問題。 c++ で、あえてメモリ確保に malloc を使用した場合、どうやって初期化すればよいか?

一般的に、メモリ確保処理は負荷が高い。 なので、c や c++ では一度に大量のメモリ確保を行う事がある。 インスタンスを 100 個作成する時、100 回メモリ確保をするより、1 回で100 個分のメモリ確保をした方がパフォーマンス的には良いのだ。

ただし、この時に new を使えない事が多々ある。 new でも複数インスタンスのメモリを一度に確保する事は可能なのだが、この場合コンストラクタに渡す引数がややこしい。 「引数無しのコンストラクタで初期化しておいて、後で各種設定を行う」で済む場合は良いのだが、そうとは限らない。

こんな場合は、初期化を行わない malloc の方が都合が良かったりする。 そうすれば、malloc でメモリ確保だけをしておいて、実際に使う直前にコンストラクタを呼び、初期化すれば良いのだから。

というわけで、malloc で確保したメモリに後からコンストラクタを走らせる方法のメモ。

#include <cstdlib>
#include <iostream>
#include <new>

using namespace std;

class Foo {
public:
  Foo(const char *msg) : msg_(msg) {}

  void hello() { cout << msg_ << endl; }

  ~Foo() {
    cout << "~";
    hello();
  }

private:
  const char *msg_;
};

void new_foo() {

  auto ptr = new Foo("new");
  ptr->hello();
  delete (ptr);
}

void malloc_foo() {

  auto ptr = malloc(sizeof(Foo));
  if (!ptr) {
    auto handler = get_new_handler();
    if (!handler)
      throw bad_alloc();
    else
      handler();
  }

  new (ptr) Foo("malloc");
  static_cast<Foo *>(ptr)->hello();
  static_cast<Foo *>(ptr)->~Foo();

  free(ptr);
}

int main() {

  new_foo();
  malloc_foo();

  return 0;
}

上記において、new_foo と malloc_foo は、ほぼ同様の働きをする。

new_foo は普通に new を使う方法だ。 new Foo ("new") で Foo クラスのインスタンスのメモリ確保、初期化を行い、メソッド hello を呼び出し、delete でメモリを解放している。

malloc_foo は、あえて malloc を使う方法。 最初にエラー処理以外の部分を見てみよう。

auto ptr = malloc(sizeof(Foo)) で Foo に必要なサイズのメモリを確保する。 その後、new (ptr) Foo("malloc") とすると、ptr に確保されたメモリ上で初期化を走らせる。 この時、コンストラクタには “malloc” という const char * の引数を渡している。

その後 new_foo と同様にメソッド hello を呼び出す。

最後にメモリ解放をするのだが、malloc で確保したメモリは delete ではなく free で解放する必要がある。 free でメモリを解放する場合は明示的にデストラクタを呼ぶ必要があるので注意だ。

ついでなのでエラー処理についてもちょっと書いておく。 new にしろ malloc にしろ、メモリ確保は必ず成功するとは限らない。エラーが発生する可能性がある。

new の場合、デフォルトでは失敗時に bad_alloc クラスの例外が投げられる。 ただし、この処理は set_new_handler という関数で変更可能だ。 関数 new_foo では、この挙動に任せており特にエラー処理はしていない。

malloc の場合、失敗時は必ず null pointer が返される。 malloc_foo では、失敗時の挙動を new と同じになるようにしている。

つまり、返り値が null pointer の場合、まず set_new_handler で new のエラー処理が変更されているか確認する。 もし変更されていなければ、new のデフォルト動作である bad_alloc クラスの例外を投げる。 変更されている場合は、その関数を呼び出す。

以上、完全な俺メモでした。