RISC-Vの自作シミュレータでLinuxを立ち上げることに成功したが、まだかなりシミュレーション速度が遅いことが心配だ。
原因はすでに解析済み。メモリアクセスが何度も発生しているためにそこが最適化できていない。
以下は前回Google Perftoolsで解析したシミュレータのボトルネック部分。
LoadMemory
と言う、メモリアクセスをつかさどっている部分にかなりのアクセスが集中している。
WalkPageTable
と言うこちらも仮想アドレスから物理アドレスへの変換を行う機構もやはりかなりのアクセスが必要になっている。
この辺りを改良するためには、TLBを実装するとよい。 という訳で、非常に簡単なTLBを実装してメモリアクセスの高速化の検討を行った。
(pprof) top Total: 19415 samples 3750 19.3% 19.3% 5083 26.2% Memory::LoadMemory 3088 15.9% 35.2% 6920 35.6% RiscvPeThread::WalkPageTable 1457 7.5% 42.7% 19198 98.9% RiscvPeThread::StepExec 1384 7.1% 49.9% 9034 46.5% RiscvPeThread::FetchMemory 1185 6.1% 56.0% 1185 6.1% CsrEnv::Riscv_Read_CSR 1173 6.0% 62.0% 1173 6.0% RiscvDec::DecodeInst 872 4.5% 66.5% 872 4.5% __memmove_avx_unaligned_erms 768 4.0% 70.4% 768 4.0% TraceInfo::RecordTrace 543 2.8% 73.2% 2056 10.6% RiscvPeThread::CheckInterrupt 397 2.0% 75.3% 3561 18.3% EnvBase::LoadMemoryDebug
RISC-VにTLBはある?
誤解を招きそうなのだが、RISC-VにTLBの仕様は明記されていない。 それはRISC-VがISAの仕様だからであり、わざわざTLBの構成まで規定する必要はないからだ。 なので、Spikeを含めTLBの実装方法はかなりバリエーションがある。 ここでは、Spikeの実装を参考にしながらTLBを実装した。
static const uint32_t tlb_width = 1 << 8; bool m_tlb_en[tlb_width]; Addr_t m_tlb_tag[tlb_width]; Addr_t m_tlb_addr[tlb_width];
ここで、さらにTLBの参照及びアップデートの機能を追加していく。
基本的には、上記で実装しているページWalkをつかさどる関数 WalkPageTable()
に追加してけば良い。
//=================== // Simple TLB Search //=================== Addr_t vaddr_vpn = (vaddr >> 12); uint8_t vaddr_tag = vaddr_vpn & (tlb_width-1); if (m_tlb_en[vaddr_tag] && m_tlb_tag[vaddr_tag] == vaddr_vpn) {
TLBにヒットしなかったときはTLBをアップデートする。
//========================== // Update Simple TLB Search //========================== DebugPrint("<Info: TLB[%d] <= 0x%016lx(0x%016lx)>\n", vaddr_tag, vaddr_vpn, *paddr & ~0x0fff); m_tlb_en [vaddr_tag] = true; m_tlb_tag [vaddr_tag] = vaddr_vpn; m_tlb_addr[vaddr_tag] = (*paddr & ~0x0fff) | (pte_val & 0x0ff);
Spikeの実装はどのようになっているのか
Spikeも似たような実装になっている。 Spikeの方は少し複雑で、命令フェッチ、Load、Storeで別々のタグが用意されている。
riscv-isa-sim/riscv/mmu.h
// If a TLB tag has TLB_CHECK_TRIGGERS set, then the MMU must check for a // trigger match before completing an access. static const reg_t TLB_CHECK_TRIGGERS = reg_t(1) << 63; tlb_entry_t tlb_data[TLB_ENTRIES]; reg_t tlb_insn_tag[TLB_ENTRIES]; reg_t tlb_load_tag[TLB_ENTRIES]; reg_t tlb_store_tag[TLB_ENTRIES];
測定結果
Linuxのブートプロセスで、どれくらい高速化されるか観測した。まあまあ速くなった。
1000000000命令実行時間 | 実行時間 |
---|---|
TLB実装前 | 195.823 |
TLB実装後(256-entries) | 128.412 |
TLB実装後(512-entries) | 128.101 |