std :: unordered_map :: clear()有什么作用?

时间:2020-09-02 17:04:02

标签: c++ std unordered-map

我有一段简单的代码:

#pragma GCC optimize ("O0")
#include <unordered_map>
int main()
{
    std::unordered_map<int, int> map;
        
    static constexpr const int MaxN = 2e6 + 69;
    map.reserve(MaxN);
    
    int t = 1000;
    while (t--)
    {
        map.clear();
    }
    
    return 0;
}

此代码的作用只是创建一个巨大的std::unordered_map,在堆上保留大量内存,同时仍将其保留为空,然后清除1000次。令我惊讶的是,执行该程序需要花费一秒钟以上的时间。

根据cppreferencestd::unordered_map::clear元素数量中是线性的,即为0,而不是存储桶数。因此,此功能在我的程序中什么也不做,并且所花费的时间不到一毫秒。

尝试进一步分析代码,我已经写过:

#pragma GCC optimize ("O0")
#include <chrono>
#include <iostream>
#include <unordered_map>

#include <map>
template <typename T>
struct verbose_pointer
{
    using element_type = T;
    T* value = nullptr;
    static std::map<T*, std::size_t> accessTimes;   
//  T & operator[](std::size_t n)
//  {
//      ++(*count);
//      return value[n];
//  }
    T * operator ->() const
    {
        ++accessTimes[value];
        return value;
    }
//  T & operator *() const
//  {
//      ++(*count);
//      return *value;
//  }
    static void operator delete(void * ptr)
    {
        T * toErase = (static_cast<verbose_pointer *>(ptr))->value;
        std::cerr << "Deleted " << toErase << std::endl;
        std::cerr << "Address " << toErase << " accessed " << accessTimes[toErase] << " times." << std::endl;
        accessTimes.erase(toErase);
        ::operator delete(toErase);
    }
    verbose_pointer(void* ptr) : value(static_cast <T*>(ptr)) 
    {
        std::cerr << "I'm constructed from pointer: " << ptr << std::endl;
    }
    
    static verbose_pointer pointer_to(T & t) { return verbose_pointer(&t); }
    ~verbose_pointer()
    {
    }
};
template <typename T>
std::map<T*, std::size_t> verbose_pointer<T>::accessTimes;
template <typename T>
class verbose_allocator
{
    public:
        using value_type = T;
        using pointer = verbose_pointer<T>;
        constexpr verbose_allocator() noexcept = default;
        constexpr verbose_allocator(const verbose_allocator & other) noexcept = default;
        template <typename U>
        constexpr verbose_allocator(const verbose_allocator<U> & other) noexcept {}
        pointer allocate(std::size_t n)
        {
            std::cout << (n * sizeof(T)) << " bytes allocated." << std::endl;
            return static_cast<pointer>(::operator new(n * sizeof(T)));
        }
        void deallocate(pointer p, std::size_t n)
        {
            std::cout << (n * sizeof(T)) << " bytes deallocated." << std::endl;
            pointer::operator delete(&p);
        }
};
int main()
{
    std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, verbose_allocator<std::pair<const int, int>>>
        verbose_map;
        
    static constexpr const int MaxN = 2e6 + 69;
    verbose_map.reserve(MaxN);
    
    auto start = std::chrono::high_resolution_clock::now(); 
        
    int t = 1000;
    while (t--)
    {
        verbose_map.clear();
    }
    
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "1000 clear() runs take " << duration.count() << " milliseconds." << std::endl;
    
    return 0;
}

我的代码输出为:

8579908 bytes allocated.
I'm constructed from pointer: 0xd09020
1000 clear() runs take 1139 milliseconds.
I'm constructed from pointer: 0xd09020
8579908 bytes deallocated.
Deleted 0xd09020
Address 0xd09020 accessed 1 times.

似乎在reserve()语句中分配了一次巨大的内存块,并且当映射超出范围时就释放了一次,就像我期望的那样。而且,该指针仅被访问一次。

那为什么1000 std::unordered_map::clear()个操作要花这么多时间? GCC的实现在这里做什么?

1 个答案:

答案 0 :(得分:8)

对于#!/bin/bash printf '\e[1;40;92m' clear printf "All TNT releases are provided\n\tfree of charge for educational and uncommercial reasons.\n" printf "Все релизы TNT предоставляются\n\tбезвозмездно для образовательных и некоммерческих целей.\n" echo "" echo "Press ENTER if you agree or close this window!" echo "Нажмите ENTER, если вы согласны, или закройте окно!" read ok echo "Please wait..." echo "Пожалуйста, подождите..." echo "" DMG=$(dirname "$0") DIR=/tmp/tnt$RANDOM rm -rf $DIR mkdir -p $DIR cp "$DMG/Manual install"/*.dmg $DIR xattr -r -d com.apple.quarantine $DIR/*.dmg &>/dev/null #if [ $? -ne 0 ]; then # echo "Failed to add a Gatekeep exception, please try manual installation!" # echo "Ошибка добавления исключения Gatekeep, установите программу вручную!" # printf '\e[39m' # exit 1 #fi mkdir -p $DIR/mount hdiutil attach -owners on -quiet -noverify -mountpoint $DIR/mount $DIR/*.dmg -shadow $DIR/shadow find $DIR/mount -maxdepth 1 \! -type l \! -path $DIR/mount -exec xattr -r -d com.apple.quarantine {} \; &>/dev/null echo "" echo "If the application fails to open wait a bit and try again!" echo "Если программа не открывается, подождите немного и попробуйте снова!" echo "" echo "Have a nice day/night!" echo "Приятного дня/вечера!" (sleep 5 && hdiutil detach -force "$DMG") & printf '\e[39m' exit 0 的无序关联容器的定义是,它分配了足够的存储桶,以使表中的加载因子小于或等于最大加载因子(如果其中包含reserve(N)个元素)容器。最大负载因子的默认值为1,因此N必须分配至少2,000,069个存储桶。

的确,reserve被指定为花费线性时间,并且复杂度要求取决于容器中元素的数量。但更准确地说:复杂度要求指定容器元素上的操作次数。例如,如果容器包含1个元素,则在调用clear()时,容器可以对该元素执行的操作数必须有一个恒定的上限。但是,除了这些操作外,容器在“簿记”上可以花费多少时间没有限制。因此,对于基于哈希的容器,clear()很有可能在不违反标准的情况下,在存储桶数量上花费额外的线性时间。

我看着libstdc++ implementation of clear()。它进行一次遍历所有元素并将其销毁的操作,然后执行clear()将所有存储桶指针重置为null。因此实际上,总是会花费额外的时间来线性化存储桶的数量,即使这是“不必要的”,因为最初没有元素。因此,使用这种实现,您的程序的1000 memset迭代将至少执行2,000,069,000个操作(假设将一个操作归零一个指针大小的内存位置)。

相关问题