openlang

CPS 합계가 Debug에서는 스택 오버플로를 일으키고, Release에서는 두 번째 함수만 일으켜요

안녕하세요 여러분,

연습 삼아 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예요).

이게 한 가지 문제인가요, 아니면 두 가지인가요? 스레드 스택만 키우기보다는 원인을 제대로 이해하고 싶어요.

영어에서 번역원문 보기

이어지는 대화 2

서로의 근거를 따라
  1. 고유 링크

    제 생각엔 문제가 두 가지예요.

    첫째, Debug는 기본값으로 tail call이 꺼져 있어서 sumK가 거기서 오버플로가 나요. fsproj에 <Tailcalls> 줄이 없어서 Debug가 그 기본값을 쓰고 있는 것 같아요. 저도 클라이언트 프로젝트에서 똑같은 일을 겪었어요. Release에서는 되는데 Debug에서는 죽어서 오후 한나절을 날렸어요.

    둘째, sumCheckedK는 try 안에서 자기 자신을 호출해요. 그건 tail position이 아니라서 Release에서도 요소마다 프레임이 하나씩 남아요. 재귀 호출을 try 밖으로 옮기세요.

    영어에서 번역원문 보기
  2. 고유 링크

    그리고 with 안에 있는 sumCheckedK rest k도 tail call이 아니에요. try는 정상인지 오류인지만 판단하게 하고 bool이나 option을 반환하게 한 다음, 두 재귀 호출은 그 뒤에서 하세요. 제 것도 같은 방식으로 고쳤어요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. 같은 Int인데 타깃이 둘이면 답도 둘이에요

    같은 소스, 같은 JSON 파일인데 타깃마다 결과가 달라요. 이건 좋지 않아요. cohort_join은 우리 트래킹 시스템에서 내보낸 JSON을 읽어서 참가자를 id 기준으로 인덱싱해요. id는 타임스탬프에 카운터를 붙여서…

    답글 0최근 활동 gleam
  2. pure 호스트 함수가 캐시되고, 인자에 쓴 값이 그냥 사라져요

    주말 프로젝트로 D로 작성한 호스트 함수를 이름으로 등록해서 쓰는 장난감 표현식 평가기를 만들었어요. registry에 memo 캐시도 넣었어요 (꽤 뿌듯해서 일주일 내내 자랑하고 다녔어요). 규칙은 이래요. 함수가 pure…

    답글 2최근 활동 d
  3. 같은 parseServices인데 잘못된 줄은 빌드할 때만 깨지나요?

    내장 테이블은 절대 안 바뀌니까 auto를 enum으로 바꿨는데, 맞죠? 이제 smtp 25(proto 없음)가 dub build를 깨뜨려요. 에러는 stderr writeln not usable at compile time…

    답글 2최근 활동 d