dpas.patch 개선 과정

QEMU 환경에서 발견한 문제와 수정 과정을 통해 커널 디버깅을 배운다

보충자료
English
← DPAS 실습 가이드로 돌아가기

1. 개요: 논문의 코드를 실제로 적용할 때

DPAS 패치는 USENIX FAST '26에 발표된 연구 결과를 Linux 커널 5.18에 적용한 것입니다. 논문의 연구 환경(실제 NVMe SSD, 특정 커널 버전)과 실습 환경(QEMU 가상 NVMe, GCC 14)은 다르기 때문에, 패치를 그대로 적용하면 빌드 오류, 런타임 비정상 동작이 발생할 수 있습니다.

이 문서는 dpas.patch를 QEMU 환경에 적용하면서 발견한 세 가지 핵심 문제와 수정 과정을 정리합니다. 각 문제의 증상 → 원인 분석 → 코드 수정 → 검증 과정은 실제 커널 개발에서의 디버깅 흐름과 동일합니다.

실습 환경과 연구 환경의 차이
항목논문 연구 환경QEMU 실습 환경
스토리지실제 NVMe SSDQEMU 가상 NVMe (RAM-backed)
타이머 정밀도TSC 기반 sub-μs에뮬레이션 오버헤드로 수 μs
I/O 스케줄러nonenone
WBT (Write Back Throttling)활성 (wbt_lat_usec=2000)미지원 (Invalid argument)
GCC 버전GCC 11-12GCC 14 (Ubuntu 24.04)

2. 수정 1: blk_rq_stats_sectors → blk_rq_sectors

증상

DPAS를 활성화해도 모드 전환이 전혀 발생하지 않습니다. switch_stat을 확인하면 pas io: 0, cp io: 0 — I/O가 DPAS 상태 머신에 진입하지 못합니다.

원인 분석

DPAS는 I/O 요청의 크기를 기반으로 통계 버킷(bucket)을 결정합니다. 이때 blk_mq_poll_stats_bkt() 함수가 호출됩니다:

// block/blk-mq.c
static int blk_mq_poll_stats_bkt(const struct request *rq)
{
    int ddir, sectors, bucket;
    ddir = rq_data_dir(rq);
    sectors = blk_rq_stats_sectors(rq);  // 원래 코드
    if (!sectors)
        return -1;  // ← 항상 여기서 -1 반환!
    ...
}

blk_rq_stats_sectors(rq)는 단순히 rq->stats_sectors를 반환합니다. 이 값은 blk_mq_start_request()에서 QUEUE_FLAG_STATS 플래그가 설정된 경우에만 기록됩니다:

// block/blk-mq.c — blk_mq_start_request()
if (test_bit(QUEUE_FLAG_STATS, &q->queue_flags)) {
    rq->io_start_time_ns = ktime_get_ns();
    rq->stats_sectors = blk_rq_sectors(rq);  // 여기서만 설정됨
}

QUEUE_FLAG_STATS는 누가 설정하는가?

이 플래그는 blk_stat_add_callback()을 호출하는 컴포넌트에 의해 활성화됩니다:

DPAS 논문의 연구 환경에서는 I/O 스케줄러가 none이지만, WBT가 활성화(wbt_lat_usec=2000)되어 있어 QUEUE_FLAG_STATS가 설정되고 blk_rq_stats_sectors()가 정상 동작합니다.

반면 QEMU 가상 NVMe 디바이스에서는 WBT가 지원되지 않아 (cat wbt_lat_usecInvalid argument) QUEUE_FLAG_STATS가 설정되지 않으며, blk_rq_stats_sectors()항상 0을 반환합니다. 결과적으로 bucket = -1이 되어 DPAS 로직이 모두 스킵됩니다.

WBT (Write Back Throttling)란?

WBT 개요 WBT는 Linux 블록 레이어의 buffered write 쓰기 제어 메커니즘입니다 (block/blk-wbt.c). 대량의 buffered write가 디바이스를 포화시키면 read 지연이 급증하는 문제(write starvation)를 방지하기 위해, write I/O의 in-flight 개수를 동적으로 제한합니다.

WBT가 동작하려면 각 I/O 요청의 완료 지연시간을 추적해야 합니다. 이를 위해 blk_stat_add_callback()을 호출하여 QUEUE_FLAG_STATS를 활성화하고, 모든 요청에 대해 stats_sectorsio_start_time_ns를 기록합니다.

wbt_lat_usec는 WBT의 목표 지연시간(마이크로초)입니다. 이 값이 0이 아니면 WBT가 활성 상태이며, 부수적으로 QUEUE_FLAG_STATS가 켜집니다. NVMe SSD에서는 보통 기본값이 설정되어 있지만, QEMU 가상 디바이스에서는 WBT 자체가 지원되지 않습니다.

수정

// block/blk-mq.c — blk_mq_poll_stats_bkt()
ddir = rq_data_dir(rq);
- sectors = blk_rq_stats_sectors(rq);
+ sectors = blk_rq_sectors(rq);

blk_rq_sectors(rq)QUEUE_FLAG_STATS와 무관하게 요청의 섹터 수를 항상 반환합니다.

교훈 커널 API에는 이름이 비슷하지만 전제 조건이 다른 함수들이 존재합니다. blk_rq_stats_sectorsQUEUE_FLAG_STATS(WBT, I/O 스케줄러 등에 의해 활성화)가 설정된 경우에만 유효하고, blk_rq_sectors는 항상 동작합니다. 동일한 코드가 환경에 따라 다르게 동작하는 이유를 이해하려면, 함수의 반환값이 어디서 설정되는지 역추적해야 합니다.

3. 수정 2: NVMe poll_queues 설정과 io_poll_delay

증상

수정 1을 적용한 후에도 DPAS의 PAS→CP 모드 전환이 발생하지 않습니다. switch_stat에서 MODE[2] (PAS)에 계속 머물러 있습니다.

원인 분석: 두 가지 누락된 설정

(a) poll_queues=0 — Poll 큐가 없음

NVMe 드라이버는 기본적으로 poll 전용 하드웨어 큐를 생성하지 않습니다 (poll_queues=0). Poll 큐가 없으면 --hipri 플래그로 요청해도 interrupt 큐로 배정되어 polling이 불가능합니다.

# 수정: NVMe 모듈 로드 시 poll 큐 생성
sudo modprobe -r nvme
sudo modprobe nvme poll_queues=2

(b) io_poll_delay=-1 — Hybrid polling 비활성

io_poll_delay의 기본값은 -1 (BLK_MQ_POLL_CLASSIC)입니다. 이 값에서는 blk_mq_poll()blk_mq_poll_hybrid()를 호출하지 않고 바로 classic busy-poll로 진입합니다.

// block/blk-mq.c — blk_mq_poll()
if (!(flags & BLK_POLL_NOSLEEP) &&
    q->poll_nsec != BLK_MQ_POLL_CLASSIC) {  // poll_nsec == -1이면 스킵
    if (blk_mq_poll_hybrid(q, cookie))      // ← DPAS sleep 로직이 여기 있음
        return 1;
}

DPAS의 핵심 로직 — QD 추적, 모드 전환 평가, adaptive sleep — 은 모두 blk_mq_poll_hybrid()blk_mq_poll_pas_nsecs() 경로에 있습니다. io_poll_delay=-1이면 이 경로가 완전히 우회됩니다.

# 수정: hybrid polling 활성화
echo 0 | sudo tee /sys/block/nvme0n1/queue/io_poll_delay
핵심 포인트 DPAS의 코드 경로 진입에는 두 가지 필수 전제가 있습니다:
  1. NVMe poll 큐 존재 (poll_queues ≥ 1)
  2. Hybrid polling 활성 (io_poll_delay ≥ 0)
이 둘이 모두 충족되어야 blk_mq_poll_pas_nsecs()가 호출됩니다.

4. 수정 3: hrtimer 해상도와 sleep 루프

증상

수정 1, 2를 적용한 후, 단일 job(numjobs=1)에서는 DPAS 모드 전환이 정상 동작합니다. 그러나 multi-job(numjobs ≥ 2, 동일 CPU)에서 PAS 모드가 유지되어야 하는데 CP 모드로 잘못 전환됩니다.

원인 분석

DPAS의 hybrid polling sleep 루프를 살펴봅시다:

// block/blk-mq.c — blk_mq_poll_hybrid() 원래 코드
do {
    set_current_state(TASK_UNINTERRUPTIBLE);
    hrtimer_sleeper_start_expires(&hs, mode);   // (1) 타이머 시작
    if (hs.task)                                // (2) 만료 여부 확인
        io_schedule();                          // (3) CPU 양보
    hrtimer_cancel(&hs.timer);
    mode = HRTIMER_MODE_ABS;
} while (hs.task && !signal_pending(current));

정상 동작 시나리오 (실제 하드웨어, 충분한 타이머 해상도):

  1. (1)에서 d_init=100ns 타이머를 설정
  2. (2)에서 hs.task != NULL (타이머 아직 안 만료)
  3. (3)에서 io_schedule() 호출 → CPU를 다른 job에 양보
  4. 다른 job이 I/O를 제출 → QD(Queue Depth) 증가
  5. QD > 1 관측 → PAS 모드 유지 (정상)

QEMU에서의 비정상 시나리오:

  1. (1)에서 타이머 설정 — 그러나 arm 과정 자체에 수 μs 소요
  2. 100ns 타이머가 arm 완료 전에 만료 → 콜백이 hs.task = NULL로 설정
  3. (2)에서 hs.task == NULLio_schedule() 스킵
  4. CPU 양보 없음 → 다른 job 실행 불가 → QD 항상 1
  5. QD == 1 → PAS→CP 잘못된 전환

hrtimer arm 과정 상세

hrtimer_sleeper_start_expires()는 내부적으로 clockevents_program_event()을 호출합니다. 이 함수는 만료 시간을 설정하기 직전에 현재 시각을 다시 읽습니다:

// kernel/time/clockevents.c
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));  // 현재 시각 재확인
if (delta <= 0)
    return -ETIME;  // 이미 만료!

spinlock 획득, red-black tree 삽입, ktime_get() 호출 등의 과정에서 수백 ns ~ 수 μs가 소요됩니다. QEMU의 타이머 에뮬레이션 오버헤드까지 더해지면, 100ns 타이머는 arm 과정에서 이미 만료될 수밖에 없습니다.

QEMU 환경에서의 검증 데이터

d_init 값별로 io_schedule() 호출 비율을 측정한 결과:

d_init (ns)io_schedule 미호출io_schedule 호출sleep 비율판정
1005,89900%sleep 전혀 안 됨
1,0005,99800%sleep 전혀 안 됨
2,00010,785150.1%거의 안 됨
3,96011555,27399.8%정상 sleep

QEMU/KVM 환경에서 hrtimer의 실효 최소 해상도는 약 2,000~4,000ns입니다. 이보다 작은 값으로 타이머를 설정하면, arm 과정에서 만료되어 io_schedule()이 스킵됩니다.

수정: always-yield 방식

if (hs.task) 체크를 제거하고 항상 io_schedule()을 호출합니다:

// block/blk-mq.c — blk_mq_poll_hybrid() 수정 코드
do {
    set_current_state(TASK_UNINTERRUPTIBLE);
    hrtimer_sleeper_start_expires(&hs, mode);
-   if (hs.task)
-       io_schedule();
+   io_schedule();  // 항상 CPU 양보
    hrtimer_cancel(&hs.timer);
    mode = HRTIMER_MODE_ABS;
} while (hs.task && !signal_pending(current));

왜 안전한가?

타이머가 이미 만료된 경우, hrtimer_wakeup 콜백이 wake_up_process()를 호출하여 프로세스 상태를 TASK_RUNNING으로 변경합니다. 이 상태에서 io_schedule()schedule()은 단순 yield (CPU 양보)로 동작하며, 즉시 복귀합니다.

이 수정은 blk_mq_poll_hybrid() 내부에만 적용되므로 polled I/O의 hybrid polling 경로에만 영향을 미치며, 커널의 나머지 부분에는 영향이 없습니다.

always-yield의 한계 이 수정은 multi-job에서 CPU 양보를 보장하지만, 부작용이 있습니다. yield 중에 I/O가 완료되면 "overslept"로 판정되어 tf (timer floor) 카운터가 증가합니다. 기본 설정(param1=0)에서는 tf > 0이면 즉시 PAS→OL→INT 전환이 발생하여, multi-job에서 DPAS가 사실상 interrupt 모드로 전락할 수 있습니다. 실제 하드웨어에서는 hrtimer 해상도가 충분하므로 이 수정이 불필요할 수 있으며, 적용 시 param1 임계값 조정이 함께 필요합니다.

5. 수정 4: Linux Hybrid Polling (LHP) 통계 수집 복구

증상

수정 1~3을 적용한 후, PAS와 DPAS는 정상 동작하지만 Linux Hybrid Polling (LHP)이 Classic Polling(CP)과 동일하게 동작합니다: CPU 사용률 99%, context switch ~1K, adaptive sleep이 전혀 발생하지 않습니다.

원인 분석

LHP는 blk_stat 콜백 프레임워크를 통해 I/O 완료 시간 통계를 수집하고, 이를 기반으로 sleep 시간을 결정합니다. 통계 수집은 요청 완료 시점에 blk_stat_add()에서 수행됩니다:

// block/blk-stat.c — blk_stat_add()
bucket = cb->bucket_fn(rq);  // blk_mq_poll_stats_bkt(rq) 호출
stat = &per_cpu_ptr(cb->cpu_stat, cpu)[bucket];
blk_rq_stat_add(stat, value);

문제는 blk_stat_add()가 호출되는 시점입니다. 요청 완료 경로에서 blk_update_request()가 먼저 호출되어 rq->__data_len = 0으로 설정된 후에 blk_stat_add()가 호출됩니다:

// block/blk-mq.c — 요청 완료 경로
blk_mq_end_request(rq, error)
  → blk_update_request(rq, error, blk_rq_bytes(rq))
       // rq->__data_len = 0 (전체 데이터 전송 완료)
  → __blk_mq_end_request(rq, error)
       → blk_stat_add(rq, now)
            // blk_rq_sectors(rq) = 0 → bucket = -1 → 통계 수집 안 됨!

수정 1에서 blk_rq_stats_sectors()blk_rq_sectors()로 변경했기 때문에, 완료 시점에 blk_rq_sectors(rq)가 0을 반환하여 bucket이 항상 -1이 됩니다. 결과적으로 LHP의 통계가 전혀 수집되지 않고, blk_mq_poll_nsecs()는 항상 0을 반환하여 sleep을 건너뜁니다.

수정 1과의 충돌
  • PAS/DPAS: blk_mq_poll_stats_bkt()를 요청이 in-flight인 상태에서 호출 → blk_rq_sectors()가 유효한 값 반환 → 정상 동작
  • LHP 통계 수집: blk_stat_add()에서 요청 완료 후 호출 → blk_rq_sectors() = 0 → 통계 수집 실패
같은 함수가 호출 시점에 따라 다르게 동작하는 것이 핵심입니다.

수정

blk_mq_start_request()에서 stats_sectorsQUEUE_FLAG_STATS와 무관하게 항상 설정합니다:

// block/blk-mq.c — blk_mq_start_request()
+ rq->stats_sectors = blk_rq_sectors(rq);  // 항상 설정
  if (test_bit(QUEUE_FLAG_STATS, &q->queue_flags)) {
      rq->io_start_time_ns = ktime_get_ns();
-     rq->stats_sectors = blk_rq_sectors(rq);  // 이전: 여기서만 설정
      rq->rq_flags |= RQF_STATS;
  }

그리고 blk_mq_poll_stats_bkt()에서 stats_sectors를 우선 사용하되, 아직 설정되지 않은 경우 blk_rq_sectors()로 fallback합니다:

// block/blk-mq.c — blk_mq_poll_stats_bkt()
+ sectors = blk_rq_stats_sectors(rq);
+ if (!sectors)
+     sectors = blk_rq_sectors(rq);

왜 이렇게 동작하는가?

호출 시점stats_sectorsblk_rq_sectors()결과
PAS/DPAS in-flight 조회유효 (submission 시 설정)유효stats_sectors 사용 → 정상
LHP 완료 시 통계 수집유효 (submission 시 설정)0 (__data_len 소진)stats_sectors 사용 → 정상
QEMU PAS (QUEUE_FLAG_STATS 미설정, 초기)0 (미설정)유효fallback → 정상
교훈 하나의 함수가 여러 코드 경로에서 서로 다른 시점에 호출될 수 있습니다. 수정 1(blk_rq_sectors)은 PAS의 in-flight 경로에서는 올바르지만, LHP의 completion 경로에서는 문제를 일으켰습니다. 커널 수정 시에는 해당 함수의 모든 호출 시점(call sites)의 전제 조건을 확인해야 합니다.

6. 수정 전후 벤치마크 비교

수정 전 (io_poll_delay=-1, blk_rq_stats_sectors)

모든 설정 오류가 있던 초기 상태에서의 결과입니다. PAS와 DPAS가 모두 classic polling(CP)과 동일한 성능을 보입니다 — hybrid polling 경로가 완전히 우회되었기 때문입니다.

ModeIOPS (j=1)비고
INT~18,000interrupt 기반
CP~22,000classic polling
PAS~22,000CP와 동일 (DPAS 미작동)
DPAS~27,000CP와 유사 (모드 전환 없음)

수정 후 (수정 1~4 모두 적용, randread 4K)

네 가지 수정을 모두 적용한 후의 결과입니다. 5개 모드 (INT, CP, LHP, PAS, DPAS)가 각각 고유한 성능 특성을 보이며, LHP가 정상적인 hybrid polling으로 동작합니다.

Modej=1j=2j=4j=8j=16
INT4,2929,29721,14840,45160,325
CP5,5265,0825,3405,6995,856
LHP5,49410,81321,02233,61350,008
PAS5,52410,70721,03624,15531,989
DPAS5,1548,95718,78439,96155,547

Randwrite 4K

Modej=1j=2j=4j=8j=16
INT7,89815,06124,97742,15862,555
CP8,0878,0728,1118,1297,978
LHP7,87515,65631,87746,00835,942
PAS7,97715,41136,32729,67949,144
DPAS7,99815,77525,92540,59559,725

CPU 사용률 (per-CPU %, randread)

Modej=1j=2j=4j=8j=16
INT9%17%22%35%50%
CP99%99%99%99%100%
LHP52%64%91%98%99%
PAS16%25%52%98%99%
DPAS92%31%26%39%48%
관찰 포인트
  • CP: busy-poll이므로 CPU를 독점합니다. multi-job에서 성능이 증가하지 않습니다 (~5.5K 고정).
  • INT: interrupt 기반이므로 CPU를 자연스럽게 양보합니다. multi-job에서 선형에 가까운 scaling을 보입니다.
  • LHP: 수정 4 이후 정상 동작. j=1에서 CPU 52% (sleep+poll 하이브리드). INT에 근접한 scaling.
  • PAS: j=1에서 CPU 16%로 가장 효율적. adaptive sleep이 LHP보다 정교하게 동작합니다.
  • DPAS: j=1은 CP 모드 (92%), j≥2에서 PAS→OL→INT 전환. 고부하에서 INT에 근접하면서 CPU 효율 최고 (j=16: 48%).

7. 이 과정에서 배울 수 있는 것

커널 디버깅 방법론

  1. 증상을 정확히 기술한다 — "모드 전환이 안 된다"가 아니라, "switch_stat에서 pas io=0, bucket이 -1을 반환한다"처럼 구체적으로.
  2. 코드 경로를 추적한다 — I/O submit(fops.c) → poll 진입(blk-mq.c) → sleep 루프 → completion. 어느 단계에서 멈추는지 확인.
  3. 전제 조건을 확인한다QUEUE_FLAG_STATS, poll_queues, io_poll_delay 등 코드가 실행되기 위한 조건을 하나씩 검증.
  4. sysfs를 활용한다 — 커널을 다시 빌드하지 않고도 switch_stat, io_poll_delay 등으로 런타임 상태를 확인.
  5. 최소 변경으로 수정한다 — 수정 1은 함수 호출 하나 변경, 수정 3은 if 한 줄 제거. 커널 수정은 영향 범위를 최소화해야 합니다.

시스템 소프트웨어의 환경 의존성

같은 소스코드라도 실행 환경에 따라 동작이 달라질 수 있습니다. hrtimer의 경우, 실제 하드웨어에서는 100ns 타이머가 정상 동작하지만 QEMU에서는 arm 오버헤드로 인해 사실상 무효화됩니다. 이는 하드웨어 추상화(abstraction)의 한계를 보여주는 사례입니다: 커널 코드는 hrtimer API가 나노초 정밀도를 제공한다고 가정하지만, 가상화 환경에서는 이 가정이 성립하지 않습니다.

수정안의 trade-off 분석

always-yield 수정은 QEMU 환경의 문제를 해결하지만, multi-job에서 새로운 부작용(PAS→OL→INT 전락)을 만듭니다. 이처럼 커널 수정은 한 문제의 해결이 다른 문제를 유발할 수 있으며, 수정의 영향을 전체 상태 머신 관점에서 분석해야 합니다.

정리
수정위치변경량효과
blk_rq_stats_sectors → blk_rq_sectorsblk-mq.c1줄DPAS 상태 머신 진입 가능
io_poll_delay=0, poll_queues=2sysfs 설정런타임hybrid polling 경로 활성화
if (hs.task) 제거blk-mq.c1줄QEMU에서 multi-job CPU 양보 보장
stats_sectors 무조건 설정 + fallbackblk-mq.c3줄LHP 통계 수집 복구, PAS 호환 유지

8. 사례: AI 도구의 올바른 활용

이 문서에 기술된 세 가지 수정은 AI 코딩 도구(Claude Code)를 활용하여 도출되었습니다. AI 도구는 커널 소스 코드를 분석하고, 증상으로부터 원인을 추론하며, 수정안을 제안하는 데 유용했습니다. 그러나 이 과정에서 AI의 설명이 정확하지 않았던 사례가 있으며, 이를 연구자가 식별하고 검증한 과정을 소개합니다.

무엇이 맞았고, 무엇이 틀렸는가

항목AI의 판단실제
문제 식별 정확 — blk_rq_stats_sectors()가 0을 반환하여 DPAS 로직이 스킵됨을 파악 맞음
수정안 정확 — blk_rq_sectors()로 교체 제안 맞음. 수정 후 DPAS 정상 동작
원인 설명 부정확 — "BFQ, kyber 같은 I/O 스케줄러가 플래그를 활성화하며, QEMU의 none 스케줄러에서는 비활성"이라고 설명 논문 환경도 none 스케줄러 사용. 스케줄러가 원인이 아님

연구자의 검증 과정

AI의 설명을 받은 연구자는 다음과 같은 의문을 제기했습니다:

"논문의 native 실험에서도 기본 none 스케줄러를 사용했는데, 스케줄러가 원인이라면 native에서도 동일한 문제가 발생했어야 하지 않는가?"

이 질문을 기점으로 논문 연구에 사용된 실제 서버에서 검증을 수행했습니다:

# 논문 환경 (native 서버) 검증
$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline        ← none 스케줄러 확인

$ cat /sys/block/nvme0n1/queue/wbt_lat_usec
2000                           ← WBT 활성 상태

# QEMU 환경 검증
$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber  ← 동일한 none 스케줄러

$ cat /sys/block/nvme0n1/queue/wbt_lat_usec
cat: Invalid argument        ← WBT 미지원!

진짜 원인은 I/O 스케줄러가 아니라 WBT (Write Back Throttling)이었습니다. Native 서버에서는 WBT가 활성화되어 QUEUE_FLAG_STATS를 설정하고 있었고, QEMU 가상 NVMe에서는 WBT 자체가 지원되지 않아 해당 플래그가 비활성 상태였습니다.

이 사례가 시사하는 것

  1. AI는 "작동하는 수정"을 빠르게 찾을 수 있다blk_rq_stats_sectorsblk_rq_sectors 교체는 정확했고, 이 수정으로 DPAS가 실제로 동작하게 되었습니다. 커널 소스 수천 줄을 분석하여 원인 후보를 좁히는 작업에서 AI 도구는 효율적입니다.
  2. AI의 "설명"은 검증이 필요하다 — AI는 QUEUE_FLAG_STATS가 스케줄러에 의해 활성화된다는 사실을 근거로 "QEMU는 none 스케줄러이므로 비활성"이라고 추론했습니다. 이 추론은 일부 맞지만, 핵심 요인(WBT)을 놓쳤습니다. 코드 분석은 맞았으나, 실제 환경의 런타임 상태까지 추론하지는 못했습니다.
  3. 최종 검증은 연구자의 몫이다 — "native도 none 스케줄러인데?"라는 질문은 해당 시스템의 실험 환경을 실제로 아는 연구자만이 제기할 수 있습니다. AI 도구는 코드를 읽을 수 있지만, 특정 서버의 런타임 설정을 알 수는 없습니다. 수정의 정확성(correctness)과 설명의 정확성(explanation)은 별개이며, 둘 다 검증해야 합니다.
AI 도구 활용 원칙 시스템 소프트웨어 연구에서 AI 도구를 활용할 때:
  • 도구로 활용하되 판단은 직접 한다 — AI가 제안한 수정을 적용하기 전에, 왜 그 수정이 필요한지 스스로 이해할 수 있어야 합니다.
  • 수정이 작동해도 설명을 의심한다 — "고쳐졌다"는 것과 "원인을 정확히 이해했다"는 것은 다릅니다. 작동하는 수정이 잘못된 이유에 기반할 수 있습니다.
  • 환경 의존적 사실은 직접 검증한다 — AI는 코드를 분석할 수 있지만, 특정 하드웨어의 런타임 상태, 드라이버 구현 차이, 실험 환경 설정은 직접 확인해야 합니다.