Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@
## 2025-02-12 - R 언어에서 반복적인 mirt 모델 생성 시 불필요한 데이터프레임 부분집합 추출 최적화
**Learning:** R에서 데이터프레임의 특정 열을 추출하는 작업(`df[cols]`)은 O(N)의 메모리 복사를 수반합니다. `autoFIPC`에서 `mirt` 모델의 파라미터를 설정하거나 호출하는 과정 중에 `newformXDataK[colnames(newFormModel@Data$data)]` 코드가 반복해서 사용되었고, 심지어 `ncol()`을 위해 단순히 개수를 구할 때도 사용되어 불필요한 메모리 할당과 오버헤드를 초래했습니다.
**Action:** 조건문이나 반복문 내부에서 불필요하게 데이터프레임 부분집합 연산이 반복되지 않도록 외부에서 한 번만 `linkedFormData <- newformXDataK[colnames(newFormModel@Data$data)]`로 캐싱(caching)한 뒤, `ncol(linkedFormData)`와 `data = linkedFormData` 형태로 재사용하여 메모리 복사와 O(N) 오버헤드를 방지해야 합니다.

## 2024-05-24 - [데이터 프레임 메모리 복사 및 함수 오버헤드 최적화]
**Learning:**
- R에서 열 이름을 추출하기 위해 `colnames(df[cols])`를 사용하는 것은 데이터 프레임을 서브셋팅할 때 불필요한 O(N) 메모리 복사를 발생시킨다는 것을 배웠습니다. `intersect(colnames(df), cols)`를 사용하면 데이터를 복사하지 않고 집합 연산만으로 동일한 결과를 훨씬 빠르게 얻을 수 있습니다.
- 고유한 non-NA 값의 갯수를 세기 위해 `length(stats::na.omit(unique(x)))`를 사용하는 경우, `na.omit()` 함수가 내부적으로 S3 메서드 디스패치 및 `na.action` 속성 할당 등을 수행하여 루프 내에서 상당한 오버헤드를 발생시킵니다. 이를 `sum(!is.na(unique(x)))`로 대체하면 이러한 오버헤드 없이 동일한 결과를 훨씬 빠르게 얻을 수 있습니다.
Comment thread
github-actions[bot] marked this conversation as resolved.
Outdated

**Action:**
- 데이터 프레임에서 특정 열들의 이름이 존재하는지 확인할 때는 항상 서브셋팅 대신 `intersect()`를 활용할 것.
- 결측값이 아닌 값들의 갯수를 셀 때는 속성 할당이나 메서드 디스패치가 없는 순수 논리 벡터의 합(`sum(!is.na())`)을 사용할 것.
15 changes: 9 additions & 6 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -620,8 +620,9 @@ autoFIPC <-
IPDItemCount <- 0

# IPD target item checking
newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)])
oldFormColNames <- colnames(oldformYDataK[colnames(oldFormModel@Data$data)])
# ⚡ Bolt: Use intersect to avoid O(N) memory copy caused by dataframe subsetting
newFormColNames <- intersect(colnames(newFormModel@Data$data), colnames(newformXDataK))
oldFormColNames <- intersect(colnames(oldFormModel@Data$data), colnames(oldformYDataK))

# ⚡ Bolt: Vectorized match() to avoid dynamic array growth overhead inside a for loop
idxNew <- match(newformCommonItemNames, newFormColNames)
Expand Down Expand Up @@ -749,8 +750,9 @@ autoFIPC <-
}
}

newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)])
oldFormColNames <- colnames(oldformYDataK[colnames(oldFormModel@Data$data)])
# ⚡ Bolt: Use intersect to avoid O(N) memory copy caused by dataframe subsetting
newFormColNames <- intersect(colnames(newFormModel@Data$data), colnames(newformXDataK))
oldFormColNames <- intersect(colnames(oldFormModel@Data$data), colnames(oldformYDataK))

# ⚡ Bolt: Cache parameter indices to avoid O(N) linear search inside loop
newScaleParmsItemIdxCache <- split(seq_len(nrow(NewScaleParms)), NewScaleParms$item)
Expand All @@ -770,8 +772,9 @@ autoFIPC <-
if (
!is.na(newFormItemName) &&
!is.na(oldFormItemName) &&
(length(stats::na.omit(unique(newFormModel@Data$data[, newFormItemName]))) ==
length(stats::na.omit(unique(oldFormModel@Data$data[, oldFormItemName]))))
# ⚡ Bolt: Use sum(!is.na()) instead of length(stats::na.omit()) to avoid method dispatch and memory allocation overhead
(sum(!is.na(unique(newFormModel@Data$data[, newFormItemName]))) ==
sum(!is.na(unique(oldFormModel@Data$data[, oldFormItemName]))))
Comment on lines +773 to +774

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Performance rewrites lack regression coverage

The existing regression test still exercises the previous na.omit() expression. Neither sum() nor the new intersect() behavior receives equivalence coverage.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

) {
message(
'applying ',
Expand Down
Loading