| /* | 
 |  * Copyright 2016 Google Inc. All Rights Reserved. | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *     http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include <algorithm> | 
 | #include <map> | 
 | #include <sstream> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "nb/allocator.h" | 
 | #include "nb/first_fit_reuse_allocator.h" | 
 | #include "nb/fixed_no_free_allocator.h" | 
 | #include "starboard/client_porting/wrap_main/wrap_main.h" | 
 | #include "starboard/common/log.h" | 
 | #include "starboard/configuration_constants.h" | 
 | #include "starboard/event.h" | 
 | #include "starboard/file.h" | 
 | #include "starboard/memory.h" | 
 | #include "starboard/system.h" | 
 | #include "starboard/time.h" | 
 | #include "starboard/types.h" | 
 |  | 
 | namespace { | 
 |  | 
 | inline bool IsAligned(void* ptr, std::size_t boundary) { | 
 |   uintptr_t ptr_as_int = reinterpret_cast<uintptr_t>(ptr); | 
 |   return ptr_as_int % boundary == 0; | 
 | } | 
 |  | 
 | // An allocation or free. | 
 | // We use 'id' rather than raw pointer since the pointer values are | 
 | // meaningless across runs. | 
 | struct AllocationCommand { | 
 |   uint32_t id; | 
 |   uint32_t size; | 
 |   uint32_t alignment; | 
 | }; | 
 |  | 
 | std::string ReadFileContent(const std::string& pathname) { | 
 |   SB_DLOG(ERROR) << pathname; | 
 |   SbFile file = | 
 |       SbFileOpen(pathname.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL); | 
 |   SB_DCHECK(SbFileIsValid(file)); | 
 |   SbFileInfo file_info; | 
 |   bool result = SbFileGetInfo(file, &file_info); | 
 |   SB_DCHECK(result); | 
 |  | 
 |   std::vector<char> buffer(file_info.size); | 
 |   int bytes_read = SbFileReadAll(file, &buffer[0], buffer.size()); | 
 |   SB_DCHECK(bytes_read == file_info.size); | 
 |   SbFileClose(file); | 
 |  | 
 |   return std::string(buffer.begin(), buffer.end()); | 
 | } | 
 |  | 
 | void LoadAllocationPlayback(std::vector<AllocationCommand>* commands, | 
 |                             const std::string& filename) { | 
 |   std::vector<char> buffer(kSbFileMaxName * 16); | 
 |  | 
 |   bool result = SbSystemGetPath(kSbSystemPathContentDirectory, buffer.data(), | 
 |                                 buffer.size()); | 
 |   SB_DCHECK(result); | 
 |   std::string path_name(buffer.begin(), buffer.end()); | 
 |   path_name += kSbFileSepChar; | 
 |   path_name += "test"; | 
 |   path_name += kSbFileSepChar; | 
 |   path_name += "nb"; | 
 |   path_name += kSbFileSepChar; | 
 |   path_name += "testdata"; | 
 |   path_name += kSbFileSepChar; | 
 |   path_name += filename; | 
 |  | 
 |   std::map<uint64_t, uint32_t> address_map; | 
 |   // Parse each line of this playback file. | 
 |   // Each line is a 64-bit hex address, and a 32-bit size and alignment. | 
 |   std::stringstream file_stream(ReadFileContent(path_name)); | 
 |  | 
 |   std::string line; | 
 |   int address_id_counter = 0; | 
 |   while (std::getline(file_stream, line)) { | 
 |     if (!line.empty() && *line.rbegin() == '\n') { | 
 |       line.resize(line.size() - 1); | 
 |     } | 
 |     // Skip empty lines and comments. | 
 |     if (line.empty() || line[0] == '#') { | 
 |       continue; | 
 |     } | 
 |  | 
 |     uint64_t address; | 
 |     uint32_t size; | 
 |     uint32_t alignment; | 
 |     std::stringstream line_stream(line); | 
 |     line_stream << std::hex; | 
 |     line_stream >> address; | 
 |     line_stream << std::dec; | 
 |     line_stream >> size >> alignment; | 
 |     SB_DCHECK(address != 0); | 
 |     // Convert the addresses into unique IDs. We don't | 
 |     // really care what the values are, just need to match up | 
 |     // allocations with frees. | 
 |     int cur_address_id; | 
 |     std::map<uint64_t, uint32_t>::iterator it = address_map.find(address); | 
 |     if (it != address_map.end()) { | 
 |       cur_address_id = (*it).second; | 
 |     } else { | 
 |       cur_address_id = address_id_counter++; | 
 |       address_map[address] = cur_address_id; | 
 |     } | 
 |  | 
 |     AllocationCommand cmd; | 
 |     cmd.id = cur_address_id; | 
 |     cmd.size = size; | 
 |     cmd.alignment = alignment; | 
 |     commands->push_back(cmd); | 
 |   } | 
 | } | 
 |  | 
 | void ReplayCommands(nb::Allocator* allocator, | 
 |                     const std::vector<AllocationCommand>& commands, | 
 |                     SbTimeMonotonic* elapsed_time, | 
 |                     std::size_t* actual_bytes_used, | 
 |                     std::size_t* actual_high_water_mark, | 
 |                     std::size_t* allocator_bytes_used) { | 
 |   SbTimeMonotonic start = SbTimeGetMonotonicNow(); | 
 |   // Execute each command in commands vector. | 
 |   // Record total bytes allocated and report. | 
 |  | 
 |   // Map from allocation IDs to pointers returned by the allocator. | 
 |   std::map<uint32_t, void*> address_table; | 
 |   // Map from allocation IDs to size requested. | 
 |   std::map<uint32_t, std::size_t> size_table; | 
 |   // How many bytes have we actually requested. | 
 |   std::size_t cur_bytes_allocated = 0; | 
 |  | 
 |   for (std::vector<AllocationCommand>::const_iterator iter = commands.begin(); | 
 |        iter != commands.end(); ++iter) { | 
 |     const AllocationCommand& cmd = *iter; | 
 |     bool is_free = cmd.size == 0 && cmd.alignment == 0; | 
 |     if (!is_free) { | 
 |       void* ptr = allocator->Allocate(cmd.size, cmd.alignment); | 
 |       SB_DCHECK(IsAligned(ptr, cmd.alignment)); | 
 |       cur_bytes_allocated += cmd.size; | 
 |       *actual_high_water_mark = | 
 |           std::max(*actual_high_water_mark, cur_bytes_allocated); | 
 |       size_table[cmd.id] = cmd.size; | 
 |       SB_DCHECK(address_table.find(cmd.id) == address_table.end()); | 
 |       address_table[cmd.id] = ptr; | 
 |     } else { | 
 |       std::map<uint32_t, void*>::iterator it = address_table.find(cmd.id); | 
 |       SB_DCHECK(it != address_table.end()); | 
 |       void* ptr = it->second; | 
 |       allocator->Free(ptr); | 
 |       cur_bytes_allocated -= size_table[cmd.id]; | 
 |       address_table.erase(it); | 
 |     } | 
 |   } | 
 |   SbTimeMonotonic end = SbTimeGetMonotonicNow(); | 
 |   *elapsed_time = end - start; | 
 |   *actual_bytes_used = cur_bytes_allocated; | 
 |   *allocator_bytes_used = allocator->GetAllocated(); | 
 |  | 
 |   // Free any remaining allocations. | 
 |   for (std::map<uint32_t, void*>::iterator it = address_table.begin(); | 
 |        it != address_table.end(); ++it) { | 
 |     allocator->Free(it->second); | 
 |   } | 
 | } | 
 |  | 
 | class DefaultAllocator : public nb::Allocator { | 
 |  public: | 
 |   void* Allocate(std::size_t size) override { return Allocate(size, 0); } | 
 |   void* Allocate(std::size_t size, std::size_t alignment) override { | 
 |     return SbMemoryAllocateAligned(alignment, size); | 
 |   } | 
 |   void Free(void* memory) override { SbMemoryDeallocate(memory); } | 
 |   std::size_t GetCapacity() const override { return 0; } | 
 |   std::size_t GetAllocated() const override { return 0; } | 
 |   void PrintAllocations() const override {} | 
 | }; | 
 |  | 
 | // TODO: Make this work with other ReuseAllocator types. | 
 | void MemoryPlaybackTest(const std::string& filename) { | 
 |   const std::size_t kFixedNoFreeMemorySize = 512 * 1024 * 1024; | 
 |   void* fixed_no_free_memory = SbMemoryAllocate(kFixedNoFreeMemorySize); | 
 |   nb::FixedNoFreeAllocator fallback_allocator(fixed_no_free_memory, | 
 |                                               kFixedNoFreeMemorySize); | 
 |   nb::FirstFitReuseAllocator reuse_allocator(&fallback_allocator, 0); | 
 |  | 
 |   std::vector<AllocationCommand> commands; | 
 |   LoadAllocationPlayback(&commands, filename); | 
 |  | 
 |   std::size_t actual_bytes_used = 0; | 
 |   std::size_t actual_high_water_mark = 0; | 
 |   std::size_t allocator_bytes_used = 0; | 
 |   SbTimeMonotonic elapsed_time; | 
 |   ReplayCommands(&reuse_allocator, commands, &elapsed_time, &actual_bytes_used, | 
 |                  &actual_high_water_mark, &allocator_bytes_used); | 
 |   std::size_t allocator_high_water_mark = reuse_allocator.GetCapacity(); | 
 |   SB_DLOG(INFO) << "Actual used: " << actual_bytes_used; | 
 |   SB_DLOG(INFO) << "Actual high water mark: " << actual_high_water_mark; | 
 |   SB_DLOG(INFO) << "Allocator used: " << allocator_bytes_used; | 
 |   SB_DLOG(INFO) << "Allocator high water mark: " << allocator_high_water_mark; | 
 |   SB_DLOG(INFO) << "Elapsed (ms): " << elapsed_time / kSbTimeMillisecond; | 
 |  | 
 |   SbMemoryDeallocate(fixed_no_free_memory); | 
 |  | 
 |   // Test again using the system allocator, to compare performance. | 
 |   DefaultAllocator default_allocator; | 
 |   ReplayCommands(&default_allocator, commands, &elapsed_time, | 
 |                  &actual_bytes_used, &actual_high_water_mark, | 
 |                  &allocator_bytes_used); | 
 |   SB_DLOG(INFO) << "Default allocator elapsed (ms): " | 
 |                 << elapsed_time / kSbTimeMillisecond; | 
 | } | 
 |  | 
 | int BenchmarkMain(int argc, char** argv) { | 
 |   const char* kBenchmarks[] = { | 
 |       "mem_history_cobalt.txt", "mem_history_gpu.txt", "mem_history_main.txt", | 
 |   }; | 
 |  | 
 |   for (std::size_t i = 0; i < SB_ARRAY_SIZE(kBenchmarks); ++i) { | 
 |     SB_DLOG(INFO) << "Memory playback test: " << kBenchmarks[i]; | 
 |     MemoryPlaybackTest(kBenchmarks[i]); | 
 |   } | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | STARBOARD_WRAP_SIMPLE_MAIN(BenchmarkMain); |