From 4d9790547fe2d0903676e15ba162caa9d11a0ba8 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:08:11 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20O(N)=20which()=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=9D=84=20O(1)=20=EC=BA=90=EC=8B=B1=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8C=80=EC=B2=B4=ED=95=98=EC=97=AC=20=EC=84=B1?= =?UTF-8?q?=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 루프 내에서 반복적으로 호출되는 O(N) 비용의 which() 검색을 루프 외부에서 split()으로 캐싱하여 O(1) 접근이 가능하도록 최적화하였습니다. 또한 불필요한 스칼라 값에 대한 paste0() 배열 생성 오버헤드를 제거하였습니다. 이를 통해 항목 수가 많을 경우 발생하던 O(N^2) 성능 병목 현상이 완화될 것으로 기대됩니다. --- .jules/bolt.md | 3 +++ R/aFIPC.R | 35 +++++++++++------------------------ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index fc04ae0..1c38f9c 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -4,3 +4,6 @@ ## 2024-07-07 - R 언어에서 데이터 프레임의 특정 항목 탐색을 캐싱하여 O(N) 검색 병목 최적화 **Learning:** R에서 반복문 내부에서 특정 조건을 만족하는 데이터의 위치를 찾기 위해 `which()`를 여러 번 반복 호출하는 것은 O(N) 시간 복잡도를 가져 매번 불필요한 배열 스캔을 유발합니다. 이는 루프의 반복 횟수가 많고, 탐색해야할 데이터가 클 수록 성능 저하의 주 원인이 됩니다. **Action:** 조건에 맞는 인덱스를 최초 탐색 시 변수에 캐싱(`newIdx`, `oldIdx` 등)하여 저장하고 이후 동일한 데이터 접근 시 캐싱된 인덱스를 사용함으로써 O(1) 수준으로 성능을 향상시킬 수 있습니다. 추가로 스칼라 값에 대한 불필요한 `paste0()` 함수 호출을 제거하여 오버헤드를 줄입니다. +## 2024-07-08 - R 언어에서 루프 내 인덱스 검색(which) O(N) 병목 최적화 +**Learning:** R에서 반복문 내부에서 특정 조건을 만족하는 데이터의 위치를 찾기 위해 `which()`를 여러 번 호출하면 매번 O(N)의 선형 탐색(linear scan)이 발생하여 데이터 크기가 클수록 성능이 크게 저하됩니다. 또한 `paste0()`를 이용한 불필요한 배열 단위 문자열 생성은 반복문 오버헤드를 가중시킵니다. +**Action:** 조건에 맞는 인덱스를 최초 한 번 `split(seq_len(nrow(df)), df$column)`를 통해 리스트 형태로 캐싱(dictionary lookup)하여 루프 외부에서 O(1) 검색 체계로 만들고, 스칼라 값에 대한 불필요한 `paste0()` 함수 호출을 최적화(`paste(..., collapse=' ')`)하여 오버헤드를 줄입니다. diff --git a/R/aFIPC.R b/R/aFIPC.R index ba4215b..750caf6 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -743,6 +743,10 @@ autoFIPC <- newFormColNames <- colnames(newformXDataK[colnames(newFormModel@Data$data)]) oldFormColNames <- 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) + oldScaleParmsItemIdxCache <- split(seq_len(nrow(OldScaleParms)), OldScaleParms$item) + for (i in seq_along(oldformCommonItemNames)) { newFormItemStr <- newformCommonItemNames[i] oldFormItemStr <- oldformCommonItemNames[i] @@ -764,34 +768,17 @@ autoFIPC <- ' as common item use' ) - newIdx <- which(NewScaleParms$item == newFormItemStr) - oldIdx <- which(OldScaleParms$item == oldFormItemStr) + # ⚡ Bolt: Use cached O(1) dictionary lookups instead of O(N) which() scans + newIdx <- newScaleParmsItemIdxCache[[newFormItemStr]] + oldIdx <- oldScaleParmsItemIdxCache[[oldFormItemStr]] - message( - ' Newform Parms: ', - paste0( - NewScaleParms[newIdx, "value"], - ' ' - ) - ) - message( - ' Oldform Parms: ', - paste0( - OldScaleParms[oldIdx, "value"], - ' ' - ) - ) + # ⚡ Bolt: Remove unnecessary paste0() array string generation overhead + message(' Newform Parms: ', paste(NewScaleParms[newIdx, "value"], collapse = ' ')) + message(' Oldform Parms: ', paste(OldScaleParms[oldIdx, "value"], collapse = ' ')) NewScaleParms[newIdx, "value"] <- OldScaleParms[oldIdx, "value"] - message( - ' Linkedform Parms: ', - paste0( - NewScaleParms[newIdx, "value"], - ' ' - ), - '\n' - ) + message(' Linkedform Parms: ', paste(NewScaleParms[newIdx, "value"], collapse = ' '), '\n') NewScaleParms[newIdx, "est"] <- FALSE