주말 프로젝트로 D로 작성한 호스트 함수를 이름으로 등록해서 쓰는 장난감 표현식 평가기를 만들었어요. registry에 memo 캐시도 넣었어요 (꽤 뿌듯해서 일주일 내내 자랑하고 다녔어요). 규칙은 이래요. 함수가 pure면 부수 효과가 없을 테니까, 이름과 출력한 인자를 합친 키로 결과를 캐시해요.
그러고 나서 받은 배열의 첫 번째 요소를 증가시키는 bump를 썼어요. 루프 단계마다 새로 counter = [0]을 만들고, bump를 호출하고, 출력해요. 매번 [1]이 나올 거라고 예상했어요. 그런데 step 0: counter=[1]이 나오고, 그다음에 step 1: counter=[0], step 2: counter=[0]이 나와요. --verbose로 보면 1단계와 2단계에서 캐시 히트가 떠요.
tick(pure 아님, 전역 변수를 증가시켜요)은 괜찮아요. twice도 괜찮고요.
시도해 본 것들: [0] 대신 new int[1]을 써 봤는데 변화 없어요. register의 delegate가 쓰기를 먹어 버리는 건가, 아니면 functionAttributes가 거짓말을 하는 건가 싶었는데, 플래그를 출력해 보니 bump는 진짜로 pure예요. 컴파일러도 아무 말 없이 받아 줬어요. (argparse는 Phobos에 있으니까 그건 아닐 거예요.)
pure가 캐시해도 안전하다는 표시 아닌가요? 왜 쓰기가 그냥 건너뛰어지는 거예요?
// host.d
pure void bump(int[] a)
{
a[0]++;
}
// cache.d
enum attrs = functionAttributes!fn;
r.cacheable[name] = (attrs & FunctionAttribute.pure_) != 0;
int[] call(ref Registry r, string name, int[] args)
{
if (r.cacheable[name])
{
auto key = name ~ args.to!string;
if (auto p = key in r.memo)
{
r.hits++;
return *p;
}
r.misses++;
auto res = r.fns[name](args);
r.memo[key] = res;
return res;
}
return r.fns[name](args);
}
// app.d
foreach (i; 0 .. opts.steps)
{
auto counter = [0];
reg.call("bump", counter);
writeln("step ", i, ": counter=", counter);
}
영어에서 번역·원문 보기Weekend project: a toy expression evaluator with host functions written in D, registered by name. I added a memo cache in the registry (pretty proud of it, I've been bragging about it all week). The rule: if a function is pure, it can't have side effects, so cache the result keyed on name plus printed args.
Then I wrote bump, which increments the first element of the array it gets. Each loop step makes a fresh counter = [0], calls bump, prints. I expected [1] every time. I get step 0: counter=[1], then step 1: counter=[0] and step 2: counter=[0]. With --verbose there's a cache hit on steps 1 and 2.
tick (not pure, bumps a global) is fine. twice is fine too.
Things I tried: new int[1] instead of [0], no change. I figured the delegate in register was eating the write, or functionAttributes was lying, but printing the flag shows bump really is pure. The compiler took it without a peep. (argparse is in Phobos, so that's not it.)
Isn't pure supposed to be the safe-to-cache marker? Why is the write just skipped?
// host.d
pure void bump(int[] a)
{
a[0]++;
}
// cache.d
enum attrs = functionAttributes!fn;
r.cacheable[name] = (attrs & FunctionAttribute.pure_) != 0;
int[] call(ref Registry r, string name, int[] args)
{
if (r.cacheable[name])
{
auto key = name ~ args.to!string;
if (auto p = key in r.memo)
{
r.hits++;
return *p;
}
r.misses++;
auto res = r.fns[name](args);
r.memo[key] = res;
return res;
}
return r.fns[name](args);
}
// app.d
foreach (i; 0 .. opts.steps)
{
auto counter = [0];
reg.call("bump", counter);
writeln("step ", i, ": counter=", counter);
}