Sniperの動作について、一度まとめてみることにした。シミュレーションをもとにトレースを作成してみる。
main() : standalone.cc - Simulatorをインスタンス化する - getTraceManager()->init() -getTraceManager()->run() - TraceThread::start() - TraceThread::run() - コアの実行モデルを取得 - トレースが存在する限り実行 - 命令キャッシュのWarmUp - handleInstructionDetailed() - 命令のデコード、命令キャッシュへの格納 - addDetailedMemoryInfo()の実行により、メモリアクセス情報を取得する - Performance Modelに命令をPushする - Performance Modelのiterate()を実行する - handleInstruction()を呼び出す
TraceManager
はトレースの管理をする。m_threads
によりTraceThread
が格納されている?
Monitor *m_monitor; std::vector<TraceThread *> m_threads; UInt32 m_num_threads_started; UInt32 m_num_threads_running; Semaphore m_done;
TraceManager()
のinit
とrun
について。
void TraceManager::init() { for (UInt32 i = 0 ; i < m_num_apps ; i++ ) { newThread(i /*app_id*/, true /*first*/, false /*init_fifo*/, false /*spawn*/, SubsecondTime::Zero(), INVALID_THREAD_ID); } }
void TraceManager::run() { start(); wait(); }
TraceManager::start()
とは何なのか。
start()
により、マネージャ内のすべてのスレッドが一斉にスタートする。
void TraceManager::start() { // Begin of region-of-interest when running Sniper inside Sniper SimRoiStart(); m_monitor->spawn(); for(std::vector<TraceThread *>::iterator it = m_threads.begin(); it != m_threads.end(); ++it) (*it)->spawn(); }
spawn()
により、スレッドが実行を開始する。
TraceThread
のspawn()
は、実際のスレッドを立ち上げて実行するものらしい。create(this)
なので、TraceThread
のrun()
を実行することになるのと思われる。
void TraceThread::spawn() { m__thread = _Thread::create(this); m__thread->run(); }
TraceThread::run()
でやっていることは、まあよくわからん。
TraceThread::run()
の動作概要
- トレースの作成を行う
// Open the trace (be sure to do this before potentially blocking on reschedule() as this causes deadlock) m_trace.initStream(); m_trace_has_pa = m_trace.getTraceHasPhysicalAddresses();
- スレッドがコアに割り当てられていない場合、スレッドのスケジューリングを行う?
if (m_thread->getCore() == NULL) { // We didn't get scheduled on startup, wait here SubsecondTime time = SubsecondTime::Zero(); m_thread->reschedule(time, NULL); }
- コアの実行モデルを取得する。パフォーマンスモデルは各CPUコアのモデルだと思われる。
Core *core = m_thread->getCore(); PerformanceModel *prfmdl = core->getPerformanceModel();
- トレースの存在する限り実行し続ける、ということ?
while(have_first && m_trace.Read(next_inst)) { if (m_blocked) { unblock(); } core = m_thread->getCore(); prfmdl = core->getPerformanceModel(); bool do_icache_warmup = false; UInt64 icache_warmup_addr = 0, icache_warmup_size = 0; /* ... 命令実行 ... */
- 命令の実行のところ。BasicBlockが過去に実行したものでなければ、命令キャッシュをWarmUpする?
if (m_bbv_end || m_bbv_last != inst.sinst->addr) { // We're the start of a new basic block core->countInstructions(m_bbv_base, m_bbv_count); // In cache-only mode, we'll want to do I-cache warmup if (m_bbv_base) { do_icache_warmup = true; icache_warmup_addr = m_bbv_base; icache_warmup_size = m_bbv_last - m_bbv_base; } // Set up new basic block info m_bbv_base = inst.sinst->addr; m_bbv_count = 0; } m_bbv_count++; m_bbv_last = inst.sinst->addr + inst.sinst->size; // Force BBV end on non-taken branches m_bbv_end = inst.is_branch;
- 命令の実行モード?サイクル測定の場合は、
InstMode::DETAILED
でhandleInstructionDetailed()
を実行するものと思われる。
switch(Sim()->getInstrumentationMode()) { case InstMode::FAST_FORWARD: break; case InstMode::CACHE_ONLY: handleInstructionWarmup(inst, next_inst, core, do_icache_warmup, icache_warmup_addr, icache_warmup_size); break; case InstMode::DETAILED: handleInstructionDetailed(inst, next_inst, prfmdl); break; default: LOG_PRINT_ERROR("Unknown instrumentation mode"); }
TraceThread::handleInstructionDetailed()
の動作概要
- 命令のデコード。一度もデコードしたことがなければデコード結果を入れる。デコード結果を取得する。命令キャッシュから命令を取得し、
DynamicInstruction
を作成する。
if (m_icache.count(inst.sinst->addr) == 0) m_icache[inst.sinst->addr] = decode(inst); // Here get the decoder instruction without checking, because we must have it for sure const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]); Instruction *ins = m_icache[inst.sinst->addr]; DynamicInstruction *dynins = prfmdl->createDynamicInstruction(ins, va2pa(inst.sinst->addr));
- 分岐命令でば、分岐命令情報を追加する。
// Add dynamic instruction info if (inst.is_branch) { dynins->addBranch(inst.taken, va2pa(next_inst.sinst->addr), dec_inst.is_indirect_branch()); }
- メモリアクセスオペランドの数だけ、
addDetailedMemoryInfo()
を実行する。この部分は、num_memory_operands()
でメモリアクセスの回数を指定いたのだが、ベクトル命令の場合はオペランドで指定できないのでトレース側からメモリアクセス情報の回数を取得している。
// Ignore memory-referencing operands in NOP instructions if (!dec_inst.is_nop()) { const bool is_prefetch = dec_inst.is_prefetch(); // for(uint32_t mem_idx = 0; mem_idx < Sim()->getDecoder()->num_memory_operands(&dec_inst); ++mem_idx) for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx) { if (Sim()->getDecoder()->op_read_mem(&dec_inst, mem_idx)) { addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::READ, is_prefetch, prfmdl); } } for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx) { if (Sim()->getDecoder()->op_write_mem(&dec_inst, mem_idx)) { addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::WRITE, is_prefetch, prfmdl); } } }
- パフォーマンスモデルに命令情報を追加し、パフォーマンスモデルを実行する
// Push instruction prfmdl->queueInstruction(dynins); // simulate prfmdl->iterate();
TraceThread::addDetailedMemoryInfo()
の概要
メモリアクセス情報を追加する。va2pa
によるアドレス変換に失敗すればno_mapping
によりaddMemory(0)
を実行する。アドレス変換に成功した場合はaddMemory(pa)
を実行する。
bool no_mapping = false; UInt64 pa = va2pa(mem_address, is_prefetch ? &no_mapping : NULL); if (no_mapping) { dynins->addMemory( inst.executed, SubsecondTime::Zero(), 0, Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx), op_type, 0, HitWhere::PREFETCH_NO_MAPPING); } else { dynins->addMemory( inst.executed, SubsecondTime::Zero(), pa, Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx), op_type, 0, HitWhere::UNKNOWN); }
addMemory()
では、メモリアクセス情報をひたすら格納している。
static const UInt8 MAX_MEMORY = 32; MemoryInfo memory_info[MAX_MEMORY]; void addMemory(bool e, SubsecondTime l, IntPtr a, UInt32 s, Operand::Direction dir, UInt32 num_misses, HitWhere::where_t hit_where) { LOG_ASSERT_ERROR(num_memory < MAX_MEMORY, "Got more than MAX_MEMORY(%d) memory operands", MAX_MEMORY); memory_info[num_memory].dir = dir; memory_info[num_memory].executed = e; memory_info[num_memory].latency = l; memory_info[num_memory].addr = a; memory_info[num_memory].size = s; memory_info[num_memory].num_misses = num_misses; memory_info[num_memory].hit_where = hit_where; num_memory++; } `````` main() : standalone.cc - Simulatorをインスタンス化する - getTraceManager()->init() -getTraceManager()->run() - TraceThread::start() - TraceThread::run() - コアの実行モデルを取得 - トレースが存在する限り実行 - 命令キャッシュのWarmUp - handleInstructionDetailed() - 命令のデコード、命令キャッシュへの格納 - addDetailedMemoryInfo()の実行により、メモリアクセス情報を取得する - Performance Modelに命令をPushする - Performance Modelのiterate()を実行する - handleInstruction()を呼び出す
Sniperの動作について、一度まとめてみることにした。シミュレーションをもとにトレースを作成してみる。
TraceManager
はトレースの管理をする。m_threads
によりTraceThread
が格納されている?
Monitor *m_monitor; std::vector<TraceThread *> m_threads; UInt32 m_num_threads_started; UInt32 m_num_threads_running; Semaphore m_done;
TraceManager()
のinit
とrun
について。
void TraceManager::init() { for (UInt32 i = 0 ; i < m_num_apps ; i++ ) { newThread(i /*app_id*/, true /*first*/, false /*init_fifo*/, false /*spawn*/, SubsecondTime::Zero(), INVALID_THREAD_ID); } }
void TraceManager::run() { start(); wait(); }
TraceManager::start()
とは何なのか。
start()
により、マネージャ内のすべてのスレッドが一斉にスタートする。
void TraceManager::start() { // Begin of region-of-interest when running Sniper inside Sniper SimRoiStart(); m_monitor->spawn(); for(std::vector<TraceThread *>::iterator it = m_threads.begin(); it != m_threads.end(); ++it) (*it)->spawn(); }
spawn()
により、スレッドが実行を開始する。
TraceThread
のspawn()
は、実際のスレッドを立ち上げて実行するものらしい。create(this)
なので、TraceThread
のrun()
を実行することになるのと思われる。
void TraceThread::spawn() { m__thread = _Thread::create(this); m__thread->run(); }
TraceThread::run()
でやっていることは、まあよくわからん。
TraceThread::run()
の動作概要
- トレースの作成を行う
// Open the trace (be sure to do this before potentially blocking on reschedule() as this causes deadlock) m_trace.initStream(); m_trace_has_pa = m_trace.getTraceHasPhysicalAddresses();
- スレッドがコアに割り当てられていない場合、スレッドのスケジューリングを行う?
if (m_thread->getCore() == NULL) { // We didn't get scheduled on startup, wait here SubsecondTime time = SubsecondTime::Zero(); m_thread->reschedule(time, NULL); }
- コアの実行モデルを取得する。パフォーマンスモデルは各CPUコアのモデルだと思われる。
Core *core = m_thread->getCore(); PerformanceModel *prfmdl = core->getPerformanceModel();
- トレースの存在する限り実行し続ける、ということ?
while(have_first && m_trace.Read(next_inst)) { if (m_blocked) { unblock(); } core = m_thread->getCore(); prfmdl = core->getPerformanceModel(); bool do_icache_warmup = false; UInt64 icache_warmup_addr = 0, icache_warmup_size = 0; /* ... 命令実行 ... */
- 命令の実行のところ。BasicBlockが過去に実行したものでなければ、命令キャッシュをWarmUpする?
if (m_bbv_end || m_bbv_last != inst.sinst->addr) { // We're the start of a new basic block core->countInstructions(m_bbv_base, m_bbv_count); // In cache-only mode, we'll want to do I-cache warmup if (m_bbv_base) { do_icache_warmup = true; icache_warmup_addr = m_bbv_base; icache_warmup_size = m_bbv_last - m_bbv_base; } // Set up new basic block info m_bbv_base = inst.sinst->addr; m_bbv_count = 0; } m_bbv_count++; m_bbv_last = inst.sinst->addr + inst.sinst->size; // Force BBV end on non-taken branches m_bbv_end = inst.is_branch;
- 命令の実行モード?サイクル測定の場合は、
InstMode::DETAILED
でhandleInstructionDetailed()
を実行するものと思われる。
switch(Sim()->getInstrumentationMode()) { case InstMode::FAST_FORWARD: break; case InstMode::CACHE_ONLY: handleInstructionWarmup(inst, next_inst, core, do_icache_warmup, icache_warmup_addr, icache_warmup_size); break; case InstMode::DETAILED: handleInstructionDetailed(inst, next_inst, prfmdl); break; default: LOG_PRINT_ERROR("Unknown instrumentation mode"); }
TraceThread::handleInstructionDetailed()
の動作概要
- 命令のデコード。一度もデコードしたことがなければデコード結果を入れる。デコード結果を取得する。命令キャッシュから命令を取得し、
DynamicInstruction
を作成する。
if (m_icache.count(inst.sinst->addr) == 0) m_icache[inst.sinst->addr] = decode(inst); // Here get the decoder instruction without checking, because we must have it for sure const dl::DecodedInst &dec_inst = *(m_decoder_cache[inst.sinst->addr]); Instruction *ins = m_icache[inst.sinst->addr]; DynamicInstruction *dynins = prfmdl->createDynamicInstruction(ins, va2pa(inst.sinst->addr));
- 分岐命令でば、分岐命令情報を追加する。
// Add dynamic instruction info if (inst.is_branch) { dynins->addBranch(inst.taken, va2pa(next_inst.sinst->addr), dec_inst.is_indirect_branch()); }
- メモリアクセスオペランドの数だけ、
addDetailedMemoryInfo()
を実行する。この部分は、num_memory_operands()
でメモリアクセスの回数を指定いたのだが、ベクトル命令の場合はオペランドで指定できないのでトレース側からメモリアクセス情報の回数を取得している。
// Ignore memory-referencing operands in NOP instructions if (!dec_inst.is_nop()) { const bool is_prefetch = dec_inst.is_prefetch(); // for(uint32_t mem_idx = 0; mem_idx < Sim()->getDecoder()->num_memory_operands(&dec_inst); ++mem_idx) for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx) { if (Sim()->getDecoder()->op_read_mem(&dec_inst, mem_idx)) { addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::READ, is_prefetch, prfmdl); } } for(uint32_t mem_idx = 0; mem_idx < inst.num_addresses; ++mem_idx) { if (Sim()->getDecoder()->op_write_mem(&dec_inst, mem_idx)) { addDetailedMemoryInfo(dynins, inst, dec_inst, mem_idx, Operand::WRITE, is_prefetch, prfmdl); } } }
- パフォーマンスモデルに命令情報を追加し、パフォーマンスモデルを実行する
// Push instruction prfmdl->queueInstruction(dynins); // simulate prfmdl->iterate();
TraceThread::addDetailedMemoryInfo()
の概要
メモリアクセス情報を追加する。va2pa
によるアドレス変換に失敗すればno_mapping
によりaddMemory(0)
を実行する。アドレス変換に成功した場合はaddMemory(pa)
を実行する。
bool no_mapping = false; UInt64 pa = va2pa(mem_address, is_prefetch ? &no_mapping : NULL); if (no_mapping) { dynins->addMemory( inst.executed, SubsecondTime::Zero(), 0, Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx), op_type, 0, HitWhere::PREFETCH_NO_MAPPING); } else { dynins->addMemory( inst.executed, SubsecondTime::Zero(), pa, Sim()->getDecoder()->size_mem_op(&decoded_inst, mem_idx), op_type, 0, HitWhere::UNKNOWN); }
addMemory()
では、メモリアクセス情報をひたすら格納している。
static const UInt8 MAX_MEMORY = 32; MemoryInfo memory_info[MAX_MEMORY]; void addMemory(bool e, SubsecondTime l, IntPtr a, UInt32 s, Operand::Direction dir, UInt32 num_misses, HitWhere::where_t hit_where) { LOG_ASSERT_ERROR(num_memory < MAX_MEMORY, "Got more than MAX_MEMORY(%d) memory operands", MAX_MEMORY); memory_info[num_memory].dir = dir; memory_info[num_memory].executed = e; memory_info[num_memory].latency = l; memory_info[num_memory].addr = a; memory_info[num_memory].size = s; memory_info[num_memory].num_misses = num_misses; memory_info[num_memory].hit_where = hit_where; num_memory++; }