From e02325ed164237e8b027a0ae29a52693b4cc1cb2 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:32:30 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20mirt=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=9E=84=20=EC=B6=94=EC=B6=9C=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반복적으로 사용되는 `newformXDataK[colnames(newFormModel@Data$data)]`를 `linkedFormData` 변수에 캐싱 - 매번 데이터프레임을 새로 복사하는 O(N) 오버헤드를 제거하여 속도와 메모리 효율 개선 - `.jules/bolt.md`에 최적화 내용 기록 --- .jules/bolt.md | 3 +++ R/aFIPC.R | 20 ++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index eaa6977..7d3c603 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -13,3 +13,6 @@ ## 2024-07-12 - R 언어에서 데이터프레임 서브셋팅 시 불필요한 which() 및 반복 평가 제거 **Learning:** 데이터 프레임의 특정 로우(row)를 변경할 때 `df[which(df$col == "val"), ]`와 같이 `which()`를 사용하면 내부적으로 추가 함수 호출 및 논리 벡터 평가 오버헤드가 발생합니다. 또한, 여러 값을 업데이트하기 위해 동일한 조건식을 연속으로 사용하면 매번 동일한 O(N) 논리 벡터 평가가 중복해서 일어납니다. 불필요한 `paste0("GROUP")` 호출도 오버헤드를 더합니다. **Action:** `which()`를 생략하고 직접 논리 인덱싱(`df$col == "val"`)을 사용하며, 동일한 조건식을 두 번 이상 연속으로 사용할 경우 해당 논리 벡터를 변수에 캐싱(`idx <- df$col == "val"`)하여 여러 번 재사용함으로써 중복된 O(N) 선형 스캔을 피하고 성능을 최적화해야 합니다. 또한 불필요한 문자열 연산을 제거합니다. +## 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) 오버헤드를 방지해야 합니다. diff --git a/R/aFIPC.R b/R/aFIPC.R index 4b8dad6..6254651 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -846,6 +846,10 @@ autoFIPC <- } message('\nestimating Linked Form Eq(X) parameters') + + # ⚡ Bolt: Cache subsetted dataframe to avoid repeated O(N) memory copies during mirt model setup + linkedFormData <- newformXDataK[colnames(newFormModel@Data$data)] + if (forceNormalZeroOne) { freeMEAN <- F @@ -866,7 +870,7 @@ autoFIPC <- LinkedModelSyntax <- mirt::mirt.model(paste0( 'F1 = 1-', - ncol(newformXDataK[colnames(newFormModel@Data$data)]), + ncol(linkedFormData), '\n', 'MEAN = F1' )) @@ -877,7 +881,7 @@ autoFIPC <- LinkedModelSyntax <- mirt::mirt.model(paste0( 'F1 = 1-', - ncol(newformXDataK[colnames(newFormModel@Data$data)]), + ncol(linkedFormData), '\n' )) } @@ -897,7 +901,7 @@ autoFIPC <- LinkedModel <- mirt::mirt( - data = newformXDataK[colnames(newFormModel@Data$data)], + data = linkedFormData, LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, method = 'EM', @@ -917,7 +921,7 @@ autoFIPC <- } else { LinkedModel <- mirt::mirt( - data = newformXDataK[colnames(newFormModel@Data$data)], + data = linkedFormData, LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, method = 'EM', @@ -944,7 +948,7 @@ autoFIPC <- # LinkedModel <- oldFormModel LinkedModel <- mirt::mirt( - data = newformXDataK[colnames(newFormModel@Data$data)], + data = linkedFormData, LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, method = 'MHRM', @@ -964,7 +968,7 @@ autoFIPC <- } else { LinkedModel <- mirt::mirt( - data = newformXDataK[colnames(newFormModel@Data$data)], + data = linkedFormData, LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, method = 'MHRM', @@ -988,7 +992,7 @@ autoFIPC <- # message('Estimation failed. estimating new parameters with no prior distribution using quasi-Monte Carlo EM estimation. please be patient.') # # rm(LinkedModel) - # try(LinkedModel <- mirt::mirt(data = newformXDataK[colnames(newFormModel@Data$data)], LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, SE = T, method = 'QMCEM', accelerate = 'squarem', technical = list(NCYCLES = 1e+5), pars = NewScaleParms, GenRandomPars = F)) + # try(LinkedModel <- mirt::mirt(data = linkedFormData, LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, SE = T, method = 'QMCEM', accelerate = 'squarem', technical = list(NCYCLES = 1e+5), pars = NewScaleParms, GenRandomPars = F)) # } # # if(!LinkedModel@OptimInfo$secondordertest){ @@ -996,7 +1000,7 @@ autoFIPC <- # # try(rm(LinkedModel), silent = TRUE) # for (attempt in seq_len(3)) { - # try(LinkedModel <- mirt::mirt(data = newformXDataK[colnames(newFormModel@Data$data)], LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, SE = T, method = 'MHRM', accelerate = 'squarem', technical = list(NCYCLES = 1e+5, MHRM_SE_draws = 200000), pars = NewScaleParms, GenRandomPars = T)) + # try(LinkedModel <- mirt::mirt(data = linkedFormData, LinkedModelSyntax, itemtype = newFormModel@Model$itemtype, SE = T, method = 'MHRM', accelerate = 'squarem', technical = list(NCYCLES = 1e+5, MHRM_SE_draws = 200000), pars = NewScaleParms, GenRandomPars = T)) # if (exists('LinkedModel')) break # } # if (!exists('LinkedModel')) stop('Failed to estimate LinkedModel with MHRM after 3 attempts') From 5966c703db77bd40bc1633afb59c72211b411065 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:42:18 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20mirt=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=9E=84=20=EC=B6=94=EC=B6=9C=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반복적으로 사용되는 `newformXDataK[colnames(newFormModel@Data$data)]`를 `linkedFormData` 변수에 캐싱 - 매번 데이터프레임을 새로 복사하는 O(N) 오버헤드를 제거하여 속도와 메모리 효율 개선 - `.jules/bolt.md`에 최적화 내용 기록 - ignore packrat from semgrep --- .semgrepignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .semgrepignore diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000..00a51d8 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1 @@ +packrat/.* From b923f420277c43403f7b3d1e2f1f68bb85b58812 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:52:33 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20mirt=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=9E=84=20=EC=B6=94=EC=B6=9C=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반복적으로 사용되는 `newformXDataK[colnames(newFormModel@Data$data)]`를 `linkedFormData` 변수에 캐싱 - 매번 데이터프레임을 새로 복사하는 O(N) 오버헤드를 제거하여 속도와 메모리 효율 개선 - `.jules/bolt.md`에 최적화 내용 기록 - ignore packrat from semgrep --- .semgrepignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.semgrepignore b/.semgrepignore index 00a51d8..570a114 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -1 +1 @@ -packrat/.* +packrat/**