openlang

열마다 새로 할당하던 루프

Julia에서 슬라이싱은 복사를 해요. 알고 있었는데 루프 안에서는 깜빡했어요. 행렬의 열을 돌면서 j마다 s += sum(A[:, j])를 하는 함수를 예로 들어 볼게요. A[:, j]가 매번 새 벡터를 만들고, 더하고, 버려요. @time을 보니 할당이 열 개수에 따라 늘어났고, 성능 팁 문서는 예상 못 한 할당을 거의 언제나 문제의 신호라고 해요.

view(A, :, j)를 쓰거나 루프 앞에 @views를 붙이면 슬라이스가 A를 가리키는 SubArray가 되고 복사가 사라져요.

궁금해진 건 @views가 또 뭘 바꾸느냐예요. 감싼 식 안의 모든 슬라이스를 뷰로 바꾸는데, 대입 오른쪽에 있는 것도 포함이에요. 그래서 @views 블록 안의 x = A[:, j]는 뷰가 되고, 나중에 x에 쓰면 A에 써져요. 알고 보면 전혀 이상하지 않아요. 그런데 루프 하나 고치려고 함수 전체에 @views를 붙였는데, 그 함수가 아래쪽 어딘가에서 일부러 열을 복사하고 있었다면 놓치기 쉬워요.

이어지는 대화 4

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

    첫 번째 @time 호출에는 컴파일이 포함되므로 그 할당은 루프의 것이 아닙니다. 읽어야 할 것은 두 번째 호출입니다. 전역 A는 그 자체로 할당을 일으키니 인수로 넘기는 게 맞습니다. 그다음 행렬 폭 두 가지를 비교합니다. 열 수에 따라 늘어나는 할당 횟수가 루프를 가리킵니다.

  2. 고유 링크

    liv_n 님 방법으로 정리됐어요. 컴파일이 아니라 루프 문제예요. 함수 안에서 A를 인수로 넘기고 두 번째 호출만 읽으면, 뷰가 없을 때는 할당이 열 수에 따라 늘어나고 루프에 @views를 붙이면 더 이상 늘지 않아요.

    @views를 함수 전체로 올리고 싶은 분께 하나 경고할게요. 제 글에서 조심하라고 했던 바로 그 함정에 그대로 빠져요.

    @views function colsums!(out, A)
        for j in axes(A, 2)
            out[j] = sum(A[:, j])
        end
        backup = A[:, 1]
        A[:, 1] .= 0
        return backup
    end

    backup은 복사본이어야 해요. @views 아래에서는 뷰라서, 0으로 채우는 줄을 지나면 이것도 전부 0이 돼요. 할당 숫자는 아주 좋아 보이는데 함수는 엉뚱한 값을 돌려줘요. 그러니 @views는 루프에만 붙여요.

  3. 고유 링크

    루프에만 붙이는 것과 함수 전체에 붙이는 것 사이에 중간 길이 있어요. @views는 어디에 있든 그대로 두고, 일부러 복사하는 줄만 copy(A[:, 1])로 쓰는 거예요. 매크로 안에서는 슬라이스가 먼저 뷰가 되고, copy가 그 뷰로 새 배열을 만들어요. 그래서 backup은 0으로 채우는 줄을 지나도 살아남아요. 매크로 밖에서는 같은 줄이 필요한 것보다 한 번 더 복사하게 되는데, 해가 되지는 않아요. 어느 쪽이든 나중에 누가 매크로 위치를 옮겨도 이 줄의 뜻은 그대로예요. 제가 신경 쓴 건 그 부분이에요.

    그다음 liv_n이 말한 방법대로 폭이 다른 두 행렬로 다시 재 보세요. 이번에는 매크로를 함수 전체에 붙인 채로요. 백업 때문에 호출마다 열 하나 크기의 할당이 한 번 더 보여야 하고, 폭에 따라 늘어나는 건 없어야 해요.

  4. 고유 링크

    개수만 보지 말고 바이트도 보세요. 두 번째 호출의 @allocated를 열 수로 나눈 값이 Float64 열 하나, 그러니까 행당 여덟 바이트쯤에 가까우면 슬라이스 때문이고 다른 건 없어요.

같은 질문의 다른 갈래

  1. 행이 하나인 행렬의 sum(X)

    관측 행렬을 받아 각 열을 X ./ sum(X)로 정규화하는 함수가 있다. 행은 관측, 열은 특성이다. 오래 문제없이 돌았다. 그러다 행이 하나뿐인 입력이 들어왔다. sum은 크기가 1이 아닌 첫 번째 차원을 따라 더한다.…

    답글 6matlab
  2. 전부 NA인 그룹에 na.rm = TRUE

    그룹별 합계를 tapply(x, g, sum, na.rm = TRUE)로 냈더니 한 그룹이 0으로 나왔다. 그 그룹의 값은 전부 NA다. 측정값이 하나도 없는데 표에는 합계가 0이라고 적혀 있다. 하나씩 보면 다 변명할 수…

    답글 4r
  3. f2py가 C 순서 배열을 호출마다 복사한다

    f2py로 NumPy 배열을 Fortran 서브루틴에 넘긴다. 배열은 평소처럼 np.zeros((n, m))로 만들었으니 C 순서다. Fortran은 열이 연속으로 놓여 있길 원한다. f2py도 그걸 알고, 일반 입력 인자라…

    답글 3fortran