当前位置: 首页 > 编程日记 > 正文

Rocksdb 内存“不释放”问题 分析

文章目录

      • 问题场景描述
      • 问题复现
        • 编写随机写 测试工具
      • 使用工具抓取内存分配过程
      • 源码分析
        • memtable逻辑
        • table_cache逻辑
      • 总结

在这里插入图片描述
整体的IO场景到底层的源码分析过程如上导图,接下来将详细阐述具体的过程。

问题场景描述

我们的rocksdb作为单机存储引擎,跑在用分布式一致性协议raft 封装的一个分布式存储集群之上。基本的IO架构图如下:
在这里插入图片描述
针对该分布式存储集群,上层使用的是随机IO ,即每个raft交给rocksdb的请求所转化的key都是随机的。此时,rocksdb底层当然调用的是put的接口来持久化key-value数据。

问题现象是(同事给出的,我们只看到一个结果) 随着IO的持续写入,大概每个节点rocksdb数据的存储量都达到20G以上之后,top看到的IO 进程物理内存资源和实际的抓取的rocksdb tcmalloc分配的堆内存大小无法匹配,差距达到2-3倍。这个时候为了排除raft对内存消耗的影响,他将raft的写log逻辑去掉,IO仅仅经过协议栈到达底层rocksdb,但是他看到的日志以及内存占用仍然还是无法匹配,且内存持续增大无法释放,是不是rocksdb内部的存在内存泄露?

问题复现

业务场景 也就是随机put,且每次都必先,那么复现就很简单了,那单独的rocksdb来进行随机写测试,并抓取内存分布情况。

编写随机写 测试工具

这里说明一下为什么不实用rocksdb原生的db_bench进行测试,它功能更多,配置更强。
但是我们想要打印我们自己想看的东西,且排除它自己工具本身接口过多而产生的干扰,所以就直接自己写一个小工具,方便易用,抓取内存信息更为方便。

使用put接口进行随机写 测试工具的封装,以下代码提供如下功能

  • 指定随机写 请求的个数
  • 指定 key的范围,默认随机
  • 指定value的大小
  • 指定rocksdb compaction线程数
  • 指定 put的客户端线程数

实现工具如下:

#ifndef __UTIL_H__
#define __UTIL_H__ #include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <thread>
#include <mutex>#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <gperftools/malloc_extension.h>#include <iostream>
#include <string>
#include <vector>#include "rocksdb/cache.h"
#include "rocksdb/db.h"
#include "rocksdb/slice.h"
#include "rocksdb/options.h"
#include "rocksdb/table.h"
#include "rocksdb/trace_reader_writer.h"
#include "rocksdb/compaction_filter.h"#define LEN 2048
static std::string NEW_VALUE = "NewValue";using namespace std;class ChangeFilter : public rocksdb::CompactionFilter {public:explicit ChangeFilter() {}bool Filter(int /*level*/, const rocksdb::Slice& /*key*/, const rocksdb::Slice& /*value*/,std::string* new_value, bool* value_changed) const override {assert(new_value != nullptr);*new_value = NEW_VALUE;*value_changed = true;return false;}const char* Name() const override { return "ChangeFilter"; }
};/*compaction filter*/
class ChangeFilterFactory : public rocksdb::CompactionFilterFactory {public:explicit ChangeFilterFactory() {}std::unique_ptr<rocksdb::CompactionFilter> CreateCompactionFilter(const rocksdb::CompactionFilter::Context& /*context*/) override {return std::unique_ptr<rocksdb::CompactionFilter>(new ChangeFilter());}const char* Name() const override { return "ChangeFilterFactory"; }
};/*input args*/
static long db_count = 1;                      // database num
static long test_count;                        // request num per database
static long key_range;                         // key range
static long value_size = 100;                  // value size
static long compaction_num = 32;               // background compaction num,if 0, with no compaction
static long thread_num = 8;                    // client thread numconst size_t long_value_len = 5 * 1024 * 1024;
static string long_value(long_value_len, 'x');mutex g_count_mutex;
static long req_num = 0;static long db_no = -1;static long parse_long(const char *s)
{char *end_ptr = nullptr;long v = strtol(s, &end_ptr, 10);assert(*s != '\0' && *end_ptr == '\0');return v;
}static double now()
{struct timeval t;gettimeofday(&t, NULL);return t.tv_sec + t.tv_usec / 1e6;
}static string long_to_str(long n)
{char s[30];sprintf(s, "%ld", n);return string(s);
}/*
初始化传入的参数:
1. 请求个数
2. key的范围
3. value的大小(默认100B)
4. rocksdb后台compaction线程数
5. 客户端压put的线程数
6. 数据库的个数(指定多少个db)
*/
static void init(int argc, char *argv[])
{assert(argc == 7);test_count = parse_long(argv[1]);key_range = parse_long(argv[2]);value_size = parse_long(argv[3]);compaction_num = parse_long(argv[4]);thread_num = parse_long(argv[5]);db_count = parse_long(argv[6]);if (key_range == 0){key_range = 1L << 62;}assert(db_count > 0 && db_count <= 20 && test_count > 0 && key_range > 0 && value_size > 0);for (long i = 0; i < db_count; ++ i){pid_t pid = fork();assert(pid >= 0);if (pid == 0){//childsignal(SIGHUP, SIG_IGN);db_no = i;break;}}if (db_no < 0){//parentsleep(1);exit(0);}srand((long)(now() * 1e6) % 100000000);
}/*生成随机key*/
static string rand_key()
{char s[30];unsigned long long n = 1;for (int i = 0; i < 4; ++ i){n *= (unsigned long long)rand();}sprintf(s, "%llu", n % (unsigned long long)key_range);string k(s);return k;
}static void set_value()
{assert(long_value.size() == long_value_len);for (size_t i = 0; i < long_value_len; ++ i){long_value[i] = (unsigned char)(rand() % 255 + 1);}}//多线程压数据库
template <class DB>
void put_thread(long thread_id, DB *db, string db_full_name, double ts,std::shared_ptr<rocksdb::Cache> cache) {while(1) {const size_t value_slice_len = value_size;char buff[2048];  memset(buff,0,sizeof(char )*2048 + 1);/*生成指定大小的value*/rocksdb::Slice rand_value(long_value.data() + rand() % (long_value_len - value_slice_len), value_slice_len);rocksdb::Status s = db->Put(rocksdb::WriteOptions(), rand_key(), rand_value);g_count_mutex.lock(); req_num ++;g_count_mutex.unlock(); if (!s.ok()){cerr << "Put failed: " << s.ToString() << endl;exit(1);}/*当线程编号为10时打印统计的信息*/if (thread_id == 10 && req_num % 10000 == 0){double tm = now() - ts;/*统计当前的IO效率*/printf("thread_id %ld %s: time=%.2f, count=%ld, speed=%.2f\n", \thread_id, db_full_name.c_str(), tm, req_num, req_num / tm);/*打印rocksdb内部统计的 内存占用指标*/db->GetProperty("rocksdb.block-cache-usage", &out);//rocksdb blockcache 组件占用内存情况fprintf(stdout, "rocksdb.block-cache-usage : %s\n", out.c_str());db->GetProperty("rocksdb.estimate-table-readers-mem", &out);fprintf(stdout, "rocksdb.estimate-table-readers-mem : %s\n", out.c_str());db->GetProperty("rocksdb.size-all-mem-tables", &out); //主要是看这个指标,代表所有的memtable内存占用情况fprintf(stdout, "rocksdb.size-all-mem-tables : %s\n", out.c_str());//tcmalloc statsMallocExtension::instance()->GetStats(buff,2048);fprintf(stdout, "simple_examples heap stats is  : %s\n", buff);fflush(stdout);}//总的IO请求达到了参数设置的请求个数,所有线程停止写入if(req_num >= test_count) {delete db;break;}}
}template <class DB, class OPT>
static void do_run(const string &db_name)
{/*初始化option选项的配置*/OPT options;options.create_if_missing = true;options.stats_dump_period_sec = 30;options.env->SetBackgroundThreads(32);options.OptimizeLevelStyleCompaction();options.allow_concurrent_memtable_write=true ;options.enable_pipelined_write=true ;options.compaction_filter_factory.reset(new ChangeFilterFactory()) ;string db_full_name = db_name + "_" + long_to_str(db_no);printf("%s: db_count=%ld, test_count=%ld, key_range=%ld\n", db_full_name.c_str(), db_count, test_count, key_range);DB *db;if(compaction_num == 0) {options.compaction_style = rocksdb::CompactionStyle::kCompactionStyleNone;} else {options.max_background_compactions = compaction_num;  }//cache inistd::shared_ptr<rocksdb::Cache> cache = rocksdb::NewLRUCache(1024L * 1024L * 1024L);rocksdb::BlockBasedTableOptions table_options;table_options.block_cache = cache;options.table_factory.reset(NewBlockBasedTableFactory(table_options));rocksdb::Status status = DB::Open(options, string("./db/") + db_full_name, &db);if (!status.ok()){cerr << "open db failed: " << status.ToString() << endl;exit(1);}double ts = now();set_value();for (long id = 0;id < thread_num; ++id ) {std::thread t(put_thread<DB>, id, db, db_full_name,ts,cache);t.detach();}printf("\n");sleep(40);   //等待最后的stat dump输出
}#endif

引用工具时只需要调用以上initdo_run两个函数,传入db名称即可

#include "util.h"int main(int argc, char *argv[])
{init(argc, argv);do_test<rocksdb::DB, rocksdb::Options>("rocksdb");
}

以上代码在逻辑中已经增加了tcmalloc 的MallocExtension::instance()->GetStats(buff,2048);接口,可以打印tcmalloc的状态信息。
使用该接口时头文件需要指定#include <gperftools/malloc_extension.h>,编译选项之中需要加入-ltcmalloc,系统找不到tcmalloc的动态库,则需要制定动态库的加载路径env LD_PRELOAD="/usr/lib/libtcmalloc.so",关于gperftools的使用配置详细可以参考gperftools

使用工具抓取内存分配过程

  1. valgrind + massif
    这里很简单,使用如下命令让进程启动:
    valgrind --tools=massif ./test_tools 10000000 0 256 32 100 1
    关于valgrind的详细使用可以参考valgrind,这里在运行过程中massif会做很多次当前进程占用的物理内存快照,并且其中会有详细快照,即进程物理内存分配过程中的一个函数层级调用栈。valigrind默认抓取的是堆内存,如需要抓取mmap之类的匿名页分配的内存,需要指定对应的参数。

    运行一段时间之后终止进程,会在当前目录下生成一个.out文件,使用ms_print查看文件内容

    结果类似如下
    在这里插入图片描述

    这里需要注意massif打印的并不是内存没有释放的,只是当前时刻进程物理内存的一个分布,但我们仍然能够看到一些由于的内存占用信息,一个是memtable创建的时候调用arena分配器分配的内存,还有一个是blockcache 存储解压缩数据的一个调用栈。

    为了让数据更加全面准确,我们也使用gperf工具进行进程堆内存分配的一个数据收集。

  2. gperf profiling + pprof数据收集
    我们使用如下方式启动进程
    env HEAPPROFILE=./rocksdb_profiling ./test_tools 10000000 0 256 32 100 1,此时同样会每隔一段时间会在当前文件夹下生成一个以rocksdb_profiling开头的heap文件
    接下来我们使用工具pprof来查看内存占用情况
    pprof --text ./test_tools ./rocksdb_profiling.0001.heap | vim -
    打印如下

    重点关注第一列和第四列,分别表示该函数当前正在使用的内存和累计分配的内存
    在这里插入图片描述
    同样为了增加对比性,使用以下命令可视化打印以上占用内存较多的函数的calltrace
    pprof --svg ./test_tools ./rocksdb_profiling.0001.heap >> rocksdb_profiling.svg
    将生成的svg文件放入浏览器如下:
    在这里插入图片描述
    因为我们打印的时候没有过滤mmap以及sbrk等分配在匿名页上的内存占用情况,导致显示的数据只有16%的部分是arena分配memtable的占用,其他大都是pthread_create创建线程时使用mmap分配的内存。

    在这里插入图片描述
    可以通过如下命令过滤掉匿名页的内存统计:
    pprof --svg --ignore='SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc' ./test_tools ./rocksdb_profiling.0001.heap >> rocksdb_profiling.svg

在运行代码的过程中我们的二进制工具也会实时打印使用内部接口抓取到的内存占用数据以及tcmalloc的接口如下:

rocksdb.block-cache-usage : 0                                                                                                                                                                                                                                                                                                                                 
rocksdb.estimate-table-readers-mem : 0                                                                                                                                                                                                                                                                                                                    
rocksdb.size-all-mem-tables : 50132416                                                                                                                                                                                                                                                                                                                        
simple_examples heap stats is  : ------------------------------------------------                                                                                                                 
MALLOC:       50844816 (   48.5 MiB) Bytes in use by application                                                                                                                                  
MALLOC: +       876544 (    0.8 MiB) Bytes in page heap freelist                                                                                                                                  
MALLOC: +       341064 (    0.3 MiB) Bytes in central cache freelist                                                                                                                              
MALLOC: +            0 (    0.0 MiB) Bytes in transfer cache freelist                                                                                                                             
MALLOC: +       366376 (    0.3 MiB) Bytes in thread cache freelists                                                                                                                              
MALLOC: +      2621440 (    2.5 MiB) Bytes in malloc metadata                                                                                                                                     
MALLOC:   ------------                                                                                                                                                                            
MALLOC: =     55050240 (   52.5 MiB) Actual memory used (physical + swap)                                                                                                                         
MALLOC: +            0 (    0.0 MiB) Bytes released to OS (aka unmapped)                                                                                                                          
MALLOC:   ------------                                                                                                                                                                            
MALLOC: =     55050240 (   52.5 MiB) Virtual address space used                                                                                                                                   
MALLOC:                                                                                                                                                                                           
MALLOC:             86              Spans in use                                                                                                                                                  
MALLOC:              7              Thread heaps in use                                                                                                                                           
MALLOC:           8192              Tcmalloc page size                                                                                                                                            
------------------------------------------------                                                                                                                                                  
Call ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).                                                                                                                    
Bytes released to the OS take up virtual address space but no physical memory.   

源码分析

通过以上两个组合工具已经抓取到了rocksdb随机写时对应的内存占用数据以及内存分配过程,且以上过程中我们抓数据的时候也都在观察top本身统计的无力内存占用大小,经过多方数据比对且数据量也能够达到业务问题的数据量,并未出现top实际的物理内存超过rocksdb本身统计的内存占用2-3倍的情况,差异最多只有20%。

带着疑惑先分析一下两个组合工具对内存数据的统计,如果在内存管理的逻辑上确实有不合理的地方那跟着内存分配的调用栈一看源码就知道了。

valgrind和gperf都统计到了arena的内存分配占用较多的情况,我们先看一下这个逻辑是否合理。

rocksdb的写入流程图如下,详细可以参考rocksdb 写入原理:
在这里插入图片描述
一个put请求会先写wal,再写memtable,由以上调用栈我们知道此时是在写memtable。同时我们上层是多个线程在put,rocksdb会为每个put绑定一个writer,并指定一个主writer 在batch_size的范围内负责先写入自己以及从writer的wal,同时从writer可以直接写memtable.

memtable逻辑

写入会与自己key-value所绑定的column family对应,从而保证cf的逻辑分区功能。
此时调用到了写入对应cf的函数,并将key-value数据添加到memtable之中:

Status PutCFImpl(uint32_t column_family_id, const Slice& key,const Slice& value, ValueType value_type) {
......MemTable* mem = cf_mems_->GetMemTable();auto* moptions = mem->GetImmutableMemTableOptions();// inplace_update_support is inconsistent with snapshots, and therefore with// any kind of transactions including the ones that use seq_per_batchassert(!seq_per_batch_ || !moptions->inplace_update_support);if (!moptions->inplace_update_support) {bool mem_res =mem->Add(sequence_, value_type, key, value,concurrent_memtable_writes_, get_post_process_info(mem),hint_per_batch_ ? &GetHintMap()[mem] : nullptr);......
}

之后就是memtale的写入,在刚开始的时候会根据传入key的大小,value的大小分配指定长度的空间

bool MemTable::Add(SequenceNumber s, ValueType type,const Slice& key, /* user key */const Slice& value, bool allow_concurrent,MemTablePostProcessInfo* post_process_info, void** hint) {// Format of an entry is concatenation of://  key_size     : varint32 of internal_key.size()//  key bytes    : char[internal_key.size()]//  value_size   : varint32 of value.size()//  value bytes  : char[value.size()]uint32_t key_size = static_cast<uint32_t>(key.size());uint32_t val_size = static_cast<uint32_t>(value.size());uint32_t internal_key_size = key_size + 8;const uint32_t encoded_len = VarintLength(internal_key_size) +internal_key_size + VarintLength(val_size) +val_size;char* buf = nullptr;std::unique_ptr<MemTableRep>& table =type == kTypeRangeDeletion ? range_del_table_ : table_;KeyHandle handle = table->Allocate(encoded_len, &buf);......
}

最终会调用到arena分配器分配指定的内存空间供数据存储

inline char* Arena::Allocate(size_t bytes) {// The semantics of what to return are a bit messy if we allow// 0-byte allocations, so we disallow them here (we don't need// them for our internal use).assert(bytes > 0);if (bytes <= alloc_bytes_remaining_) {unaligned_alloc_ptr_ -= bytes;alloc_bytes_remaining_ -= bytes;return unaligned_alloc_ptr_;}return AllocateFallback(bytes, false /* unaligned */);
}

所以以上逻辑本身就是一个正常的内存使用逻辑,key-value写入需要写写入到memtable之中,所以会分配对应空间来保存。同时关于memtable的释放,我们并不会一直占用memtable的空间,而是当memtable写入超过以上流程图显示的write_buffer_size的大小之后,会将当前memtable标记为只读的immutable memtable,从而开始向底层固化,并且会创建一个新的memtable来继续接受key-vale
内存中能够同时存在的memtable的个数取决于参数max_write_buffer_number,也就是immutable memtable向底层固化结束之后会被删除。

到此arena的分配即为正常的逻辑处理。

table_cache逻辑

但是在valgrind之中仍然有另一个内存占用较大的函数calltrace
UncompressBlockContentsForCompressionType

仍然先看以上的流程图,我们能够看到memtable是一个rocksdb存在于内存的数据结构,除了该文件之外rocksdb还有一些其他的数据结构用来管理存储在sst之上的key-value数据,以及一些常驻于内存用于提升索引性能的数据结构,他们都被封装在了block cache之中。同时另一个cache组件 tablecache是用来管理rocksdb内部产生读数据的一个存储组件,比如compaction过程中需要挑选每一层向下一层写入的文件的时候会将改文件的一些元数据读取到table cache之中,如果有过压缩,则会进行解压。

对应的逻辑如下:
当上层触发读的时候,会下发一个key,table_cache就站出来想要对当前读的数据做一个缓存来减少磁盘IO的次数,此时读请求会先下发到table_cache之下,拿着请求的key 从tablecache中的data block中索引key对应的value存储位置,这个时候主要是调用FindTable这个函数,在该函数中调用GetTableReader函数创建用于缓存key不在的情况的handle信息。

Status TableCache::FindTable(const FileOptions& file_options,const InternalKeyComparator& internal_comparator,const FileDescriptor& fd, Cache::Handle** handle,const SliceTransform* prefix_extractor,const bool no_io, bool record_read_stats,HistogramImpl* file_read_hist, bool skip_filters,int level,bool prefetch_index_and_filter_in_cache) {PERF_TIMER_GUARD_WITH_ENV(find_table_nanos, ioptions_.env);Status s;uint64_t number = fd.GetNumber();Slice key = GetSliceForFileNumber(&number);*handle = cache_->Lookup(key); //先从cache中查找该key是否存在,并返回一个cache句柄TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:0",const_cast<bool*>(&no_io));/*如果没有找到,且判读此时没有IO,那么直接返回该key不存在。如果此时有IO,则会加锁再尝试找一次(防止先put后get这样的情况,put的IO链路还未完成)如果还是没有找到,则新建一个用于存放hanlde的缓存table_reader,放到table cache之中,用作当前key数据的缓存*/if (*handle == nullptr) { if (no_io) {  // Don't do IO and return a not-found statusreturn Status::Incomplete("Table not found in table_cache, no_io is set");}MutexLock load_lock(loader_mutex_.get(key));// We check the cache again under loading mutex*handle = cache_->Lookup(key);if (*handle != nullptr) {return s;}//尝试新建一个table_reader,用来存放handle的缓存std::unique_ptr<TableReader> table_reader;s = GetTableReader(file_options, internal_comparator, fd,false /* sequential mode */, record_read_stats,file_read_hist, &table_reader, prefix_extractor,skip_filters, level, prefetch_index_and_filter_in_cache);if (!s.ok()) {assert(table_reader == nullptr);RecordTick(ioptions_.statistics, NO_FILE_ERRORS);// We do not cache error results so that if the error is transient,// or somebody repairs the file, we recover automatically.} else {// 如果创建成功了,就把table_reader添加到cache之中s = cache_->Insert(key, table_reader.get(), 1, &DeleteEntry<TableReader>,handle);if (s.ok()) {// Release ownership of table reader.// 添加成功之后,就把用于缓存hanlde 的table_reader释放掉,table_reader.release();}}}return s;
}

接下来看一下GetTableReader 函数,主要是通过NewTableReader函数来进行table_reader的创建

Status TableCache::GetTableReader(const FileOptions& file_options,const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,bool sequential_mode, bool record_read_stats, HistogramImpl* file_read_hist,std::unique_ptr<TableReader>* table_reader,const SliceTransform* prefix_extractor, bool skip_filters, int level,bool prefetch_index_and_filter_in_cache) {......s = ioptions_.table_factory->NewTableReader(TableReaderOptions(ioptions_, prefix_extractor, file_options,internal_comparator, skip_filters, immortal_tables_,level, fd.largest_seqno, block_cache_tracer_),std::move(file_reader), fd.GetFileSize(), table_reader,prefetch_index_and_filter_in_cache);...
}

最终的calltrace就如我们之前看到的打印栈一样,核心还是在没有从table_cache之中检测到key之后想要将key所代表的hanlde添加到cache之中,这个过程在完成之后会释放掉中间生成的临时数据结构table_reader(它是用来缓存key在table_cache中的数据的)。
在这里插入图片描述

总结

综上的源码分析,这样的memtable和table_cache 内存分配是完全属于正常逻辑,且持续大压力put的过程中并未复现内存问题。

于是带着数据、分析过程和源码 与同事进行核对,他也百思不得其解,无奈之下只好让他重新pull 最新代码,再来一轮测试。

那么奇迹出现了,他反复得按照之前的测试方式,rocksdb内存资源依旧稳若泰山。。。。。。最终呢,之前测试的代码版本是一波异常raft的处理逻辑,正常IO的时候会在内存缓存大量的临时数据结构无法释放,且不属于raft log,属于测试代码。今天重新搞了一波最终版本,一切重归于好。

计算机系统已经不再是一套简单系统,一个微小得改动就可能耗费几个人一天的时间,而能够规避这样的问题最好的办法就是引入一套完善严谨的规则来约束,缩小复杂系统的复杂度。

相关文章:

GitHub上整理的一些工具【转载】

技术站点Hacker News&#xff1a;非常棒的针对编程的链接聚合网站Programming reddit&#xff1a;同上MSDN&#xff1a;微软相关的官方技术集中地&#xff0c;主要是文档类infoq&#xff1a;企业级应用&#xff0c;关注软件开发领域OSChina&#xff1a;开源技术社区&#xff0c…

show在php,show.php

我的留言板function dodel(id){if(confirm("确定要删除么&#xff1f;")){window.location del.php?idid;}}我的留言板添加留言查看留言查看留言留言标题留言人留言内容IP地址留言时间操作// 获取留言信息&#xff0c;解析后输出到表格中// 1.从留言liuyan.txt中获取…

#天天复制,今天写一个# 把文字转为图片

/*** 把文字转为图片* * param text* 要写的内容* throws IOException*/public static void textToImg(String text) throws IOException {int len text.length();int fontSize 1000;int width len * fontSize;Font font new Font("楷体", Font2D.NAT…

spark(3) - scala独立编程

>>非集成&#xff1a; 环境需要 * spark 2.4.0 * scala 2.11.12 * sbt &#xff08;打包&#xff09; 开发过程 1、shell命令下创建项目目录结构 *****/ project / src / main / scala -> . / ClassName.scala &#xff08; touch gedit 命令&#xff09; …

C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览

文章目录STL六大部件容器的使用Arrayvectorlistdequemutisetmultimapunordered_multiset/set使用一个东西&#xff0c;却不明白它的道理&#xff0c;不高明。STL六大部件 容器 Containers 用来存放数据分配器 Allocators 为容器内的数据分配存储空间算法 Algorithms 计算数据迭…

Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析

文章转载至CSDN社区罗升阳的安卓之旅&#xff0c;原文地址&#xff1a;http://blog.csdn.net/luoshengyang/article/details/8570428 通过前面几篇文章的学习&#xff0c;我们知道了在 Android系统中&#xff0c;无论是普通的Activity窗口&#xff0c;还是特殊的输入法窗口和壁…

oracle非归档模式下如何备份,Oracle之RMAN数据库在非归档模式下的备份和恢复

1.数据库在非归档模式下的备份 SQLgt; archive log list;数据库日志模式 非存档模式自动存档 禁用存档终点 USE_DB_RECOVERY_FIL1.数据库在非归档模式下的备份SQL> archive log list;数据库日志模式 非存档模式自动存档 禁用存档终点 USE_DB_RECOVERY_FILE_DEST最早的联机日…

C# 视频多人脸识别的实现

上一篇内容的调整&#xff0c;提交到git了&#xff0c;https://github.com/catzhou2002/ArcFaceDemo基本思路如下&#xff1a;一、识别线程1.获取当前图片2.识别当前图片的人脸位置&#xff0c;并将结果存入列表3.分别获取人脸的特征值并比对&#xff0c;并将结果存入列表4.如果…

C++ STL: 分配器allocators 源码分析

STL 基本的六大组件作用以及功能如下&#xff1a; 可以看到allocator是数据存储组件container的幕后支持者&#xff0c;负责为其数据存储分配对应的存储空间。 operator::new 在详细介绍alloctor之前&#xff0c;先描述一下new运算符&#xff0c;我们使用C new一个对象的时候…

android xUtils的使用

gethub地址&#xff1a;https://github.com/wyouflf/xUtils/ xUtils简介 xUtils 包含了很多实用的android工具。xUtils 支持大文件上传&#xff0c;更全面的http请求协议支持(10种谓词)&#xff0c;拥有更加灵活的ORM&#xff0c;更多的事件注解支持且不受混淆影响...xUitls 最…

oracle 条件反转,Oracle反转倒置函数

Oracle提供了一个反转倒置函数reverse&#xff0c;但此函数不能分组倒置&#xff0c;本文提供了一个即可分组倒置的函数&#xff0c;如下所示&#xff1a;CREATE OR REPLACE FUNCTION REVERSE_F(p_str VARCHAR2, p_delimiter VARCHAR2:)RETURN VARCHAR2 ISv_return VARCHAR2(40…

android读取大图片并缓存

最近开发电视版的云存储应用&#xff0c;要求”我的相册“模块有全屏预览图片的功能&#xff0c;全屏分辨率是1920*1080超清。UI组件方面采用GalleryImageSwitcher组合&#xff0c;这里略过&#xff0c;详情参见google Android API。相册图片预取缓存策略是内存缓存&#xff08…

[ZJOI2018]历史

Description: 给定一棵树,定义每个点的操作为把这个点到1号点的路径覆盖上颜色i,每次该点到1号点经过的不同颜色段数会加到答案中,要使所有点按某一顺序操作完后答案最大 给定每个点要执行的操作次数,并给出m次修改,问每次修改后的最大答案 Hint: \(n,m \le 4*10^5\) Solution:…

C++ STL: lower_bound 和 upper_bound

接口声明 以下有两个不同的版本 lower_bound template <class ForwardIterator, class T>ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val);template <class ForwardIterator, class T, class Compare>ForwardItera…

1199: 房间安排

1199: 房间安排 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 1 Solved: 1[Submit][Status][Web Board]Description 2010年上海世界博览会(Expo2010),是第41届世界博览会。于2010年5月1日至10月31日期间&#xff0c;在中国上海市举行。本次世博会也是由中国举办的首届世界…

oracle判断非空并拼接,oracle sql 判断字段非空,数据不重复,插入多跳数据

&#xfeff;&#xfeff;oracle sql 判断字段非空&#xff0c;数据不重复select distinct(mobile) from wx_user_mobile where active_time is not nullbegininsert into sms_submit(id,gateway_id,source_number,dest_number,message_content)values(sms_system.nextval,1,88…

量产工具介绍(2)

前面介绍了量产工具概念&#xff0c;U盘构造&#xff0c;量产工具获取途径&#xff0c;以及国内的芯片分类&#xff0c;今天&#xff0c;我们从注意事项及常见问题继续介绍量产相关知识注意事项厂家推出的量产工具也是在不断提高版本的&#xff0c;新版本添加有新主控型号驱动。…

C++ STL: 容器vector源码分析

文章目录前言vector的核心接口vector push_back实现vector 的 Allocatorvector 的 push_back总结前言 vector 是我们CSTL中经常使用的一个容器&#xff0c;提供容量可以动态增长&#xff0c;并且随机访问内部元素的数据存储功能。 这个容器我们最常使用&#xff0c;所以也是最…

STM32学习笔记9(SysTick滴答时钟)

我不得不说意法半导体确实有点风骚&#xff01;甚至有点变态。我对ST文档 STM32F10XXX参考手册的编辑水平真是不敢恭维。手册中好多说明都是含糊不清&#xff0c;甚至将好多对初学者来说很重要的地方都一笔带过&#xff0c;让人着实摸不着头脑。比如前面我说过的关于NVIC嵌套向…

oracle存储空间管理,Oracle存储空间管理

Oracle存储空间管理1.查看每个数据文件的剩余表空间(一个表空间只对应N个数据文件,N一般等于1)主要是利用表dba_free_space(表空间剩余空间状况)和dba_data_files(数据文件空间占用情况)select b.file_id  "文件ID",b.tablespace_name  "表空间名",b.f…

漫谈Httpclient

引用地址&#xff1a; http://hc.apache.org/httpclient-3.x/ End of life The Commons HttpClient project is now end of life, and is no longer being developed. It has been replaced by the Apache HttpComponents project in its HttpClient andHttpCore modules, whic…

具体数学-扰动法

from scipy.special import combn int (input("n: "))k int (input("k: "))sum1 0for j in range(0, k):for i in range(0, n1):sum1 sum1 (i**j)*comb(k1,j)result ((n1)**(k1) - sum1)/comb(k1,k) print(result) python 中计算排列组合&#xff1a…

C++ STL: 超详细 容器 deque 以及 适配器queue 和 stack 源码分析

文章目录前言deque 实现deque类_Deque_iterator 类deque 的元素插入 insert函数deque如何模拟空间连续queue 实现stack 的实现前言 C容器中 deque是一个非常有趣的容器结构&#xff0c;我们知道deque本身是一个支持双向扩容的结构&#xff0c;对外表现出连续的存储方式。 本节将…

php好的mvc中index方法,创建一个mvc应用目录架构并创建入口文件index.php

摘要&#xff1a;<?php require vendor/autoload.php;require pig/Base.php;define(ROOT_PATH,__DIR__./);$configrequire pig/config.php;$queryStr$_S<?php require vendor/autoload.php;require pig/Base.php;define(ROOT_PATH,__DIR__./);$configrequire pig/confi…

C语言对mysql数据库的操作

C语言对mysql数据库的操作 原文:C语言对mysql数据库的操作这已经是一相当老的话题。不过今天我才首次使用&#xff0c;把今天的一些体会写下来&#xff0c;也许能给一些新手带来一定的帮助&#xff0c;更重要的是供自己今后忘记的怎么使用而进行查阅的&#xff01; 我们言归正传…

[转]MCC(移动国家码)和 MNC(移动网络码)

From : http://blog.chinaunix.net/uid-20484604-id-1941290.html 国际移动用户识别码&#xff08;IMSI&#xff09; international mobile subscriber identity 国际上为唯一识别一个移动用户所分配的号码。  从技术上讲&#xff0c;IMSI可以彻底解决国际漫游问题。但是由于…

虚拟机cenos 重置密码

许久不用虚拟机&#xff0c;临时想登录看下&#xff0c;结果想不起来密码了&#xff0c;尝试各种可能的密码都登录失败。然后百度查验各种方法&#xff0c;看到一篇文章然后根据上面说的成功解决了问题&#xff0c;贴出链接&#xff1a;https://blog.csdn.net/dannistang/artic…

g-git 相关命令 及其 基本原理探索 (一)

文章目录git 最小配置作用域git 创建本地仓库git log 查看版本演进.git 目录refs目录objectsgit 三种对象类型详解 (commit ,tree,blob)因为工作需求&#xff0c;接下来将从git的使用到其内部工作原理&#xff0c;来避免代码提交或者review或者版本管理上的一些尴尬&#xff0c…

php表单退出怎么写,PHP提交表单失败后如何保留填写的信息

[导读]本文章来给各位同学介绍PHP提交表单失败后如何保留填写的信息一些方法总结&#xff0c;最常用的就是使用缓存方式了&#xff0c;这种方法如果网速慢是可能出问题的&#xff0c;最好的办法就是使用ajax了。1&#xff0e;使用header头设置缓存控制头Cache-c本文章来给各位同…

lists,tuples and sets of Python

(python2.7.x) Lists 列表 列表是一个有序序列(集合)&#xff0c;可变的&#xff08;可以修改&#xff09;&#xff0c;可以理解为其他语言中的数组类型&#xff0c;但是列表更灵活更强大。 列表由方括号[]来定义的&#xff0c;它的元素可以是任意类型或对象&#xff0c;一个列…