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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
## 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) 오버헤드를 방지해야 합니다.
## 2025-05-18 - R 언어에서 데이터 프레임 연산 병목 최적화
**Learning:** 2차원 데이터 프레임 할당(`df[idx, "col"] <- val`) 방식은 메서드 디스패치 오버헤드를 발생시킵니다. 또한 `as.factor()`를 통한 자동 레벨 추론이나 `colnames(df[cols])`와 같이 단순히 열 이름을 추출하기 위해 데이터 프레임을 서브셋팅하는 작업은 중복된 메모리 할당 및 복사 오버헤드를 유발하여 성능을 크게 저하시킵니다.
**Action:** 2차원 할당을 직접적인 벡터 서브셋팅(`df$col[idx] <- val`)으로 대체하고, 열 이름 매칭을 위해 O(N) 서브셋팅 대신 `intersect(colnames(df), cols)`를 사용해야 합니다. 팩터 변환 시에는 수준 추론 오버헤드를 피하기 위해 `factor(..., levels=c(...))`와 같이 사전에 레벨을 명시적으로 정의해야 합니다.
58 changes: 29 additions & 29 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -598,30 +598,30 @@ autoFIPC <-
# Preserve mirt's structural estimability flags. Forcing every row TRUE
# frees boundary parameters such as 2PL g/u and makes the Hessian unstable.

NewScaleParms[NewScaleParms$item == 'GROUP', "est"] <- FALSE
OldScaleParms[OldScaleParms$item == 'GROUP', "est"] <- FALSE
NewScaleParms$est[NewScaleParms$item == 'GROUP'] <- FALSE
OldScaleParms$est[OldScaleParms$item == 'GROUP'] <- FALSE

NewScaleParms[NewScaleParms$name == "COV_11", "est"] <- TRUE
OldScaleParms[OldScaleParms$name == "COV_11", "est"] <- TRUE
NewScaleParms$est[NewScaleParms$name == "COV_11"] <- TRUE
OldScaleParms$est[OldScaleParms$name == "COV_11"] <- TRUE

if (itemtype == 'Rasch') {
NewScaleParms[NewScaleParms$name == "a1", "est"] <- FALSE
OldScaleParms[OldScaleParms$name == "a1", "est"] <- FALSE
NewScaleParms$est[NewScaleParms$name == "a1"] <- FALSE
OldScaleParms$est[OldScaleParms$name == "a1"] <- FALSE
}

#IPD
if (checkIPD == T) {
# config
IPDgroup <-
as.factor(c(
factor(c(
rep('oldForm', nrow(oldformYDataK)),
rep('newForm', nrow(newformXDataK))
))
), levels = c('oldForm', 'newForm'))
IPDItemCount <- 0

# IPD target item checking
newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)])
oldFormColNames <- colnames(oldformYDataK[colnames(oldFormModel@Data$data)])
newFormColNames <- intersect(colnames(newformXDataK), colnames(newFormModel@Data$data))
oldFormColNames <- intersect(colnames(oldformYDataK), colnames(oldFormModel@Data$data))
Comment thread
github-actions[bot] marked this conversation as resolved.
Outdated

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

newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)])
oldFormColNames <- colnames(oldformYDataK[colnames(oldFormModel@Data$data)])
newFormColNames <- intersect(colnames(newformXDataK), colnames(newFormModel@Data$data))
oldFormColNames <- intersect(colnames(oldformYDataK), colnames(oldFormModel@Data$data))

# ⚡ Bolt: Cache parameter indices to avoid O(N) linear search inside loop
newScaleParmsItemIdxCache <- split(seq_len(nrow(NewScaleParms)), NewScaleParms$item)
Expand Down Expand Up @@ -786,14 +786,14 @@ autoFIPC <-
oldIdx <- oldScaleParmsItemIdxCache[[oldFormItemStr]]

# ⚡ Bolt: Remove unnecessary paste0() array string generation overhead
message(' Newform Parms: ', paste(NewScaleParms[newIdx, "value"], collapse = ' '))
message(' Oldform Parms: ', paste(OldScaleParms[oldIdx, "value"], collapse = ' '))
message(' Newform Parms: ', paste(NewScaleParms$value[newIdx], collapse = ' '))
message(' Oldform Parms: ', paste(OldScaleParms$value[oldIdx], collapse = ' '))

NewScaleParms[newIdx, "value"] <-
OldScaleParms[oldIdx, "value"]
message(' Linkedform Parms: ', paste(NewScaleParms[newIdx, "value"], collapse = ' '), '\n')
NewScaleParms$value[newIdx] <-
OldScaleParms$value[oldIdx]
message(' Linkedform Parms: ', paste(NewScaleParms$value[newIdx], collapse = ' '), '\n')

NewScaleParms[newIdx, "est"] <-
NewScaleParms$est[newIdx] <-
FALSE
} else {
message(
Expand All @@ -813,9 +813,9 @@ autoFIPC <-
newBetaIdx <- NewScaleParms$item == 'BETA'
oldBetaIdx <- OldScaleParms$item == 'BETA'

NewScaleParms[newBetaIdx, "value"] <-
OldScaleParms[oldBetaIdx, "value"]
NewScaleParms[newBetaIdx, "est"] <-
NewScaleParms$value[newBetaIdx] <-
OldScaleParms$value[oldBetaIdx]
NewScaleParms$est[newBetaIdx] <-
FALSE

message('applying BETA parameter as linking')
Expand Down Expand Up @@ -858,13 +858,13 @@ autoFIPC <-
new_mean11_idx <- NewScaleParms$name == "MEAN_11"
old_mean11_idx <- OldScaleParms$name == "MEAN_11"

NewScaleParms[new_cov11_idx, "est"] <- FALSE
OldScaleParms[old_cov11_idx, "est"] <- FALSE
NewScaleParms[new_mean11_idx, "est"] <- FALSE
OldScaleParms[old_mean11_idx, "est"] <- FALSE
NewScaleParms$est[new_cov11_idx] <- FALSE
OldScaleParms$est[old_cov11_idx] <- FALSE
NewScaleParms$est[new_mean11_idx] <- FALSE
OldScaleParms$est[old_mean11_idx] <- FALSE

NewScaleParms[new_cov11_idx, "value"] <- 1
OldScaleParms[old_mean11_idx, "value"] <- 0
NewScaleParms$value[new_cov11_idx] <- 1
OldScaleParms$value[old_mean11_idx] <- 0
Comment thread
github-actions[bot] marked this conversation as resolved.
Outdated
}
if (freeMEAN == T) {
LinkedModelSyntax <-
Expand All @@ -875,8 +875,8 @@ autoFIPC <-
'MEAN = F1'
))

NewScaleParms[NewScaleParms$name == "MEAN_1", "est"] <- TRUE
OldScaleParms[OldScaleParms$name == "MEAN_1", "est"] <- TRUE
NewScaleParms$est[NewScaleParms$name == "MEAN_1"] <- TRUE
OldScaleParms$est[OldScaleParms$name == "MEAN_1"] <- TRUE
} else {
LinkedModelSyntax <-
mirt::mirt.model(paste0(
Expand Down
Loading