안녕하세요 여러분,
연습 삼아 continuation-passing style을 공부하고 있고, 데이터 작업용 작은 센서 도구를 쓰고 있어요. loadReadings는 리스트를 생성하고, 아직 못 만든 CSV 로더를 대신하고 있어요. 그다음 값을 두 가지 방식으로 합산해요. 제가 이해하기로는 둘 다 재귀 호출이 마지막에 일어나도록 작성했어요.
let rec sumK (xs: int list) (k: int -> int) =
match xs with
| [] -> k 0
| x :: rest -> sumK rest (fun s -> k (s + x))
let rec sumCheckedK (xs: Reading list) (k: int -> int) =
match xs with
| [] -> k 0
| r :: rest ->
try
if r.Value < 0 then
failwithf "faulty reading from %s" r.Sensor
sumCheckedK rest (fun s -> k (s + r.Value))
with ex ->
log ex.Message
sumCheckedK rest k
main에서는 sumK values id를 호출하고, 그다음 sumCheckedK readings id를 호출해요. 기본값인 1,000,000개의 readings를 쓰고 있고 (250000번째마다 하나씩 -1인 불량값이에요).
dotnet run (Debug): 합계가 나오기 전에 거의 바로 Stack overflow.와 함께 엄청나게 긴 반복 trace가 나오면서 죽어요.
dotnet run -c Release: sumK 합계는 제대로 출력되고, 그다음에 다시 Stack overflow.가 나와요. 반복되는 프레임에 sumCheckedK가 보여요.
-- 10000으로 실행하면 두 빌드 모두 잘 돌아가요.
처음에는 리스트 생성이 문제인가 의심했는데, Release 실행을 보면 그건 괜찮은 것 같아요. 그다음엔 JIT이 깊게 중첩된 클로저를 포기하는 건가 싶었어요. fsproj에는 특별한 설정을 하지 않았어요 (net8.0, .NET 8 SDK, M 시리즈 Mac의 Rider예요).
이게 한 가지 문제인가요, 아니면 두 가지인가요? 스레드 스택만 키우기보다는 원인을 제대로 이해하고 싶어요.
영어에서 번역·원문 보기Hello all,
I am working through continuation-passing style as an exercise, using a small sensor tool for my data work. loadReadings generates a list and stands in for a CSV loader I have not written yet. Then I total the values in two ways. Both are written so the recursive call is the last thing that happens, as I understand it.
let rec sumK (xs: int list) (k: int -> int) =
match xs with
| [] -> k 0
| x :: rest -> sumK rest (fun s -> k (s + x))
let rec sumCheckedK (xs: Reading list) (k: int -> int) =
match xs with
| [] -> k 0
| r :: rest ->
try
if r.Value < 0 then
failwithf "faulty reading from %s" r.Sensor
sumCheckedK rest (fun s -> k (s + r.Value))
with ex ->
log ex.Message
sumCheckedK rest k
In main I call sumK values id, then sumCheckedK readings id, with the default 1,000,000 readings (every 250000th is a faulty -1).
dotnet run (Debug): dies almost immediately with Stack overflow. and a huge repeating trace, before any total.
dotnet run -c Release: the sumK total prints correctly, then Stack overflow. again, with a repeating frame mentioning sumCheckedK.
- With
-- 10000 both builds work.
My first suspicion was the list construction, but the Release run shows that is fine. Then I wondered whether the JIT gives up on deeply nested closures. I do not set anything special in the fsproj (net8.0, .NET 8 SDK, Rider on an M-series Mac).
Is this one problem or two? I would rather understand it than just enlarge the thread stack.