—ฅ/ᐠ. ̫ .ᐟ\ฅ —

My Laboratory

[NVMeVirt] Implementation of DFTL #3 : write

WIFI-Aircat 2025. 9. 24. 15:33
🌟 Memory Computing and Computer Architecture Lab
NVMeVirt Weekly Study

- 이전글

 

 

[NVMeVirt] implementation of DFTL #2 : read

🌟 Memory Computing and Computer Architecture LabNVMeVirt Weekly Seminar [NVMeVirt] implementation of DFTL : init & rm🌟 Memory Computing and Computer Architecture LabNVMeVirt Weekly Seminar- 논문 리뷰로 알아보는 DFTL(참고) Paper Review : DF

wifiaircat.tistory.com


- conv_write 분석

이 write 분석에 시간을 얼마나 썼는지... 이해가 안 돼서 오래 걸렸다.

DFTL은 read랑 더 연관 있는데 write 분석이 왜 이리 어렵나요?

 

기존 매핑이 있을 시 `mark_page_invalid`로 기존 매핑을 invalidate하고

새 valid page를 받아오고 `mark_page_valid` 하는 부분이 문제였다.

 

  • LPN → maptbl 조회 (기존 ppa 있으면 invalid + rmap 해제)
    → new page 할당 → update maptbl and rmap

 

지금도 100% 클리어하게 머릿속에 들어온 건 아니지만

선배에게 설명 듣고 그린 그림처럼 이해하고 있다.

기존 lpn이 사용되어 있으면 ppa를 inv한다. rmap도 초기화한다
덮어쓸 수 없으므로 새 페이지를 받아서 새로운 ppa를 할당한다.
쓰기 페이지들을 모아뒀다가 마지막 write면 실제로 쓰기.

 

마음으로 받아들이기 너무 어려운 너란 존재. (●_●)–ε/̵͇̿/’̿’̿ ̿ ̿ ̿

열심히 이해하려고 했는데 이거 아니면 진짜 슬플 듯... ε/̵͇̿/’̿’̿  ̿ ̿ ̿ (●_●)

 

- write 흐름 그리기

역시 본격적인 구현에 앞서 흐름도를 그려본다.

별 것 아닌 그림 같지만 고민을 많이 했다는 것이에요...


- 1차 구현(POINT1)

	struct cmt_entry *victim = NULL;
	int victim_index = -1;
	
	
	//(Omitted)
	
	for (lpn = start_lpn; lpn <= end_lpn; lpn++) {
		uint64_t local_lpn;
		uint64_t nsecs_completed = 0;
		struct ppa ppa;

		conv_ftl = &conv_ftls[lpn % nr_parts];
		local_lpn = lpn / nr_parts;
        
		/* Check whether the given LPN has been written before */
		ppa = get_cmt_ent(conv_ftl, local_lpn); // POINT1
		//ppa = get_maptbl_ent(conv_ftl, local_lpn);
		/* get_cmt_ent is including a logic of miss control */
        
		if (mapped_ppa(&ppa)) {
			/* update old page information first */
			mark_page_invalid(conv_ftl, &ppa);
			set_rmap_ent(conv_ftl, INVALID_LPN, &ppa);
			NVMEV_DEBUG("%s: %lld is invalid, ", __func__, ppa2pgidx(conv_ftl, &ppa));
		}
        
        
   	// Will be continued to POINT2...

LPN이 쓰인 적 있는지 체크할 때 ppa를 `get_cmt_ent`에서 가져오도록 변경한다.

 

- 2차 구현(POINT2)

그리고 새로운 쓰기를 할 때 cmt에만 하도록 로직을 변경해야 한다.

		/* new write */ // POINT2
		/* is cmt full?  */
		if (conv_ftl->cmtsize == CMT_SIZE){
		victim_index = select_victim_cmt_ent(conv_ftl);
		NVMEV_ASSERT(victim_index >= 0 && victim_index < CMT_SIZE);

		victim = &conv_ftl->cmt[victim_index];

		/* write-back if dirty */
		if (victim->dirty){
			NVMEV_DEBUG("[DFTL] write-back victim: lpn=%llu\n", victim->lpn);
			set_maptbl_ent(conv_ftl, victim->lpn, &victim->ppa);
		}

		/* eviction */
		victim->ppa.ppa = UNMAPPED_PPA;
		victim->dirty = false;
		victim->valid = false;
		conv_ftl->cmtsize--;
		}

		/* real write site after checking */
		ppa = get_new_page(conv_ftl, USER_IO);
		insert_cmt_ent(conv_ftl, local_lpn, &ppa);
		//set_maptbl_ent(conv_ftl, local_lpn, &ppa);
		NVMEV_DEBUG("%s: got new ppa %lld, ", __func__, ppa2pgidx(conv_ftl, &ppa));
		
		/* update rmap */
		set_rmap_ent(conv_ftl, local_lpn, &ppa);

		mark_page_valid(conv_ftl, &ppa);

		/* need to advance the write pointer here */
		advance_write_pointer(conv_ftl, USER_IO);

		/* Aggregate write io in flash page */
		if (last_pg_in_wordline(conv_ftl, &ppa)) {
			swr.ppa = &ppa;

			nsecs_completed = ssd_advance_nand(conv_ftl->ssd, &swr);
			nsecs_latest = max(nsecs_completed, nsecs_latest);

			schedule_internal_operation(req->sq_id, nsecs_completed, wbuf,
						    spp->pgs_per_oneshotpg * spp->pgsz);
		}

		consume_write_credit(conv_ftl);
		check_and_refill_write_credit(conv_ftl);
	}
  • read에서 했던 것처럼 miss control과 같은 로직이 필요하다
  • write에서 evict되는 상황은 같다 : cmt가 full
  • 새 ppa에 대해서 lpn을 연결해야 하니까 : insert
  • 원래가 maptbl은 다 갖고 있으니까 insert가 필요X, 그래서 set 사용했음

- 3차 구현(수정)

위와 같은 로직이 반복되는 곳이 여러 군데라서 miss control을 함수화 하여

코드의 가독성을 높이고 간단하게 수정하도록 한다. 

 

- miss control 함수

cmt가 full인지 확인 → victim 대상을 고름 → ditry하면 write-back →evict

static miss_control(struct conv_ftl *conv_ftl){
	struct cmt_entry *victim = NULL;
	int victim_index = -1;

	/* is cmt full? */
	if (conv_ftl->cmtsize == CMT_SIZE){
		victim_index = select_victim_cmt_ent(conv_ftl);
		NVMEV_ASSERT(victim_index >= 0 && victim_index < CMT_SIZE);

		victim = &conv_ftl->cmt[victim_index];

		/* write-back if dirty */
		if (victim->dirty){
			NVMEV_DEBUG("[DFTL] write-back victim: lpn=%llu\n", victim->lpn);
			set_maptbl_ent(conv_ftl, victim->lpn, &victim->ppa);
		}

		/* eviction */
		victim->ppa.ppa = UNMAPPED_PPA;
		victim->dirty = false;
		victim->valid = false;
		conv_ftl->cmtsize--;
	}
	return;
}

`miss_control` 함수에 들어가도 내부에서 cmt is full이 아니면 if문에 들어가지 않고 return을 진행한다.

 

같은 로직이 반복되는 `get_cmt_ent`, `gc_write_page`, `conv_write`의 miss control부를 대체한다.

`conv_write`도 이렇게 간단하게 바뀔 수 있다~!

		/* real new write is after checking cmt */
		miss_control(conv_ftl);

		ppa = get_new_page(conv_ftl, USER_IO);
		insert_cmt_ent(conv_ftl, local_lpn, &ppa);
		//set_maptbl_ent(conv_ftl, local_lpn, &ppa);
		NVMEV_DEBUG("%s: got new ppa %lld, ", __func__, ppa2pgidx(conv_ftl, &ppa));

 

- gc_write_page

이것은 어렵지 않음. write와 같은 로직으로 수정한다.

static uint64_t gc_write_page(struct conv_ftl *conv_ftl, struct ppa *old_ppa)
{
	struct ssdparams *spp = &conv_ftl->ssd->sp;
	struct convparams *cpp = &conv_ftl->cp;
	struct ppa new_ppa;
	uint64_t lpn = get_rmap_ent(conv_ftl, old_ppa);

	NVMEV_ASSERT(valid_lpn(conv_ftl, lpn));
	
	/* real new write is after checking cmt */
	miss_control(conv_ftl);

	new_ppa = get_new_page(conv_ftl, GC_IO);
	insert_cmt_ent(conv_ftl, lpn, &new_ppa);
	//set_maptbl_ent(conv_ftl, lpn, &new_ppa);

	/* update rmap */
	set_rmap_ent(conv_ftl, lpn, &new_ppa);

	mark_page_valid(conv_ftl, &new_ppa);
	//obmitted below

- 4차 구현(수정)

나중에 평가하면서 피드백을 받고 수정했던 부분을 여기에 덧붙인다.

Cache size가 커지면 hit ratio는 0%에 가까워져야 하는데 50%에 수렴하는 문제가 있었다.

멘토님께 피드백을 받은 내용은 `prev_ppa`와 `cur_ppa` 구성할 때 `get_cmt_ent`를 두 번 호출해서

같은 LPN이 두 번 들어갔고 처음에 miss가 난 후에 무조건 hit가 나서 1:1로 비슷해진다는 것이다.

 

그래서 `start_lpn`과 `local_lpn`이 같으면 `prev_ppa`를 바로 사용하도록 변경하였다.

		/* normal IO read path */
		for (lpn = start_lpn; lpn <= end_lpn; lpn += nr_parts) {
			uint64_t local_lpn;
			struct ppa cur_ppa;

			// Here...
			local_lpn = lpn / nr_parts;
			if (start_lpn == local_lpn){
				prev_ppa = get_cmt_ent(conv_ftl, start_lpn / nr_parts);
				cur_ppa = prev_ppa;
			} else {
				cur_ppa =get_cmt_ent(conv_ftl, local_lpn)
			}
			//cur_ppa = get_cmt_ent(conv_ftl, local_lpn);
			//cur_ppa = get_maptbl_ent(conv_ftl, local_lpn);

 

Hit ratio가 50%로 수렴하는 문제 해결 완료...!


- 헷갈리는 개념

  • SSD - Channel - Die - Plane - Block(erase 단위) - Page(최소 rw 단위) - Sector(host io 단위)
  • LBA(slba, nr_lab) : host가 요청하는 주소(sector 단위)
  • LPN : sector 단위 → flash page 단위 변환한 것 start_lpn = lba / secs_per_pg
  • PPA : real NAND 내부의 page 위치 (channel, block, page 번호 포함)

바보 흔적. 바로 maptbl 보면 왜 안 돼?

  그것이 DFTL이니까.... (ㅋㅋㅋㅋㅋ)

 

여전히 순조롭지 않았다... 하지만 이제 평가를 해야 한다.

내가 만든 DFTL 타당성까지 내가 증명해야 한다.

무를 뽑아서 칼을 썰고 있다 내가... _(:ι」∠)_


반응형