Redis源码解析——有序整数集
有序整数集是Redis源码中一个以大尾(big endian)形式存储,由小到大排列且无重复的整型集合。它存储的类型包括16位、32位和64位的整型数。在介绍这个库的实现前,我们还需要先熟悉下大小尾内存存储机制。(转载请指明出于breaksoftware的csdn博客)
大小尾(Big Endian/Little Endian)
第一次接触这个概念还是在大学时上“计算机原理”时,当时只是简单的知道它的特点,但是丝毫没有深究它们产生的原因和特点。这次借着解析Redis源码,重新学习了一下大小尾。
如果进行过逆向的同学一般都会知道ESP指针的作用,它指向当前函数栈Frame的栈顶。相应的,EBP寄存器指向当前函数栈Frame的栈底。在逆向的汇编代码中,我们会发现一般使用EBP结合偏移的方法去表示栈上的一个变量,如:
mov eax, dword ptr [ebp+0ch]
mov ebx, dword ptr [ebp+08h]
但是为什么没有使用ESP表示变量呢?这是因为ESP指针在当前调用堆栈中也不是稳定的,比如我们进行函数调用,会将一些信息进行入栈处理,这个时候栈顶指针就会减小以扩大有效栈的区域。为什么是栈顶值减小以扩大栈区域呢?这是因为栈结构的特点:栈底地址比栈顶地址大。
我们再看下大小尾数据在栈空间的布局。
大尾结构将数据的高位放在地址低处,而小尾结构将数据的高位放在地址的高位。于是我们看下0x00123456在不同结构中的布局
如果我们直接在内存中查看,则是这样展现的
可以发现大尾的结构比较正常,它展现的数据的形式和人类的认知方式是相同的——高位在前,低位在后。但是小尾的展现形式则比较反人类——需要倒着看。但是存在即合理,那么小尾结构那么反人类为什么它还存在呢?
这就要从CPU的历史讲起来。历史上关于选择大尾还是小尾有着很多争论,各派都有自己的理论依据。我在网上找到一篇关于这段历史的文档——《ON HOLY WARS AND A PLEA FOR PEACE》,鉴于文档稍长,我没有仔细阅读,有兴趣的读者可以去了解下。我在这儿简单讲一下我个人的认识:大尾结构便于人类理解,小尾结构便于计算机计算。
大尾结构便于人类理解,这点我们在上面的图中已经发现了。现行的网络传输协议也是采用大尾结构,很明显作为协议,其重点是协议的可被理解性,而非其参与计算的能力。
小尾结构便于计算机计算怎么理解呢?举个例子,比如我们要对上面数据(假设为a)执行加法操作,操作数是0x12efcdab。那么计算机取到a的地址是0xFF000000,然后用操作数中的0xab与0xFF000000地址的数据进行add操作,操作结果还保存在0xFF000000中;如果有进位,则参与到操作数0xcd与0xFF000001地址的数据相加中。CPU只要对操作数和被操作数的地址向后移动取值相加就可以了。我们再想像下大尾数据的处理方法,如果也是从地址低位开始计算——即是数据高位,则可能产生回溯的问题——数据低位计算有进位则要求改之前计算的值——甚至还要改之前的之前计算的值。如果从地址高位开始计算——即数据低位,则有一次通过a地址(地址低位)跳转到地址高位的过程。可能有人会说你为什么不拿减法例子呢?人类的减法操作都是从高位向低位进行的。但是计算机没有减法器——它是通过加法操作进行减法运算的。
大小尾虽然是一个非常古老的问题,但是我们在进行数据跨网络交互时要考虑,因为网络字节序和本机字节序可能不一样。跨语言传输数据时也要考虑,像Java的数据就是大尾结构的。
Redis在源码的endianconv.c提供了一系列小尾结构向大尾结构转换的方法。我们看一下64位数据的处理函数:
/* Toggle the 64 bit unsigned integer pointed by *p from little endian to* big endian */
void memrev64(void *p) {unsigned char *x = p, t;t = x[0];x[0] = x[7];x[7] = t;t = x[1];x[1] = x[6];x[6] = t;t = x[2];x[2] = x[5];x[5] = t;t = x[3];x[3] = x[4];x[4] = t;
}uint64_t intrev64(uint64_t v) {memrev64(&v);return v;
}
Redis使用大尾也印证了我之前的观点,因为Redis是重要的功能是存储和网络交互,而非进行数值计算。
接下来我们看看Redis的有序整数集的保存结构。
基础结构
typedef struct intset {uint32_t encoding;uint32_t length;int8_t contents[];
} intset;
encoding是表示该结构保存的数据类型。它可以是下列类型值:
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
至于结构中encoding最终设置为何种类型,则要视其存储的最大数据的类型来决定。比如该结构最初始的内容只有0x08,则类型是INTSET_ENC_INT16。而如果加入了0x12345678,则类型将变成INTSET_ENC_INT32。具体的判断方法是看数值的范围:
static uint8_t _intsetValueEncoding(int64_t v) {if (v < INT32_MIN || v > INT32_MAX)return INTSET_ENC_INT64;else if (v < INT16_MIN || v > INT16_MAX)return INTSET_ENC_INT32;elsereturn INTSET_ENC_INT16;
}
不要被上面的区间名给欺骗了,它们的定义如下。
#define INT8_MIN ((int8_t)_I8_MIN)
#define INT8_MAX _I8_MAX
#define INT16_MIN ((int16_t)_I16_MIN)
#define INT16_MAX _I16_MAX
#define INT32_MIN ((int32_t)_I32_MIN)
#define INT32_MAX _I32_MAX
_IXX_MIN都是负数,其最高位为1,这样使用无符号类型强转之后便是更高区间的最大值。
intset结构中length字段表示contents数组元素的个数;contents则是整型数数组的首地址,但是不要被它类型int8_t欺骗了,它实际存储的类型可能是int32_t或者int64_t。
创建集合
集合创建通过下面方法实现
intset *intsetNew(void) {intset *is = zmalloc(sizeof(intset));is->encoding = intrev32ifbe(INTSET_ENC_INT16);is->length = 0;return is;
}
可见集合结构是在堆上分配的,初始类型是INTSET_ENC_INT16,元素个数是0。此时contents指针还是无效的,这说明该结构可能没有采用预分配空间的设计,而是实时分配。之后的代码也印证了这点。
重分配集合空间
因为inset结构是个可变长度结构,其可变部分就是contents数组的长度,所以重分配集合空间主要是根据集合保存的数据类型和数组元素个数重新分配空间。
static intset *intsetResize(intset *is, uint32_t len) {uint32_t size = len*intrev32ifbe(is->encoding);is = zrealloc(is,sizeof(intset)+size);return is;
}
获取集合长度
即返回intset的length字段,它表示其保存的数字个数
/* Return intset length */
uint32_t intsetLen(intset *is) {return intrev32ifbe(is->length);
}
获取集合占用空间大小
集合结构的设计说明其是一个可变长结构,所以计算空间大小要把结构的头大小和可变长度数组长度相加
/* Return intset blob size in bytes. */
size_t intsetBlobLen(intset *is) {return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
}
通过位置设置值
因为contents保存的数值长度要视intset的encoding类型决定,所以通过位置定位元素时,需要将contents强转为相应类型的指针。这样通过加法操作,可以让指针步进的长度为元素类型的长度
static void _intsetSet(intset *is, int pos, int64_t value) {uint32_t encoding = intrev32ifbe(is->encoding);if (encoding == INTSET_ENC_INT64) {((int64_t*)is->contents)[pos] = value;memrev64ifbe(((int64_t*)is->contents)+pos);} else if (encoding == INTSET_ENC_INT32) {((int32_t*)is->contents)[pos] = value;memrev32ifbe(((int32_t*)is->contents)+pos);} else {((int16_t*)is->contents)[pos] = value;memrev16ifbe(((int16_t*)is->contents)+pos);}
}
通过位置获取值
获取值时同样要根据intset保存的数据类型决定对contents进行加法操作时步进的长度
/* Return the value at pos, given an encoding. */
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {int64_t v64;int32_t v32;int16_t v16;if (enc == INTSET_ENC_INT64) {memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));memrev64ifbe(&v64);return v64;} else if (enc == INTSET_ENC_INT32) {memcpy(&v32,((int32_t*)is->contents)+pos,sizeof(v32));memrev32ifbe(&v32);return v32;} else {memcpy(&v16,((int16_t*)is->contents)+pos,sizeof(v16));memrev16ifbe(&v16);return v16;}
}/* Return the value at pos, using the configured encoding. */
static int64_t _intsetGet(intset *is, int pos) {return _intsetGetEncoded(is,pos,intrev32ifbe(is->encoding));
}
查找元素
查找元素时,先看待查找的元素数值是否在该集合可以表达的数值空间之内。如果不在则直接认为找不到元素,这样可以免去查找操作
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;int64_t cur = -1;/* The value can never be found when the set is empty */if (intrev32ifbe(is->length) == 0) {if (pos) *pos = 0;return 0;}
由于intset保存的是有序数字,且数字从小到大排列。这样如果元素数值比第一个元素小,或者比最后一个元素大,则说明待查元素也不在数组中。
else {/* Check for the case where we know we cannot find the value,* but do know the insert position. */if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {if (pos) *pos = intrev32ifbe(is->length);return 0;} else if (value < _intsetGet(is,0)) {if (pos) *pos = 0;return 0;}}
其他情况则说明待查元素,这个时候就采用二分查找的方式进行
while(max >= min) {mid = ((unsigned int)min + (unsigned int)max) >> 1;cur = _intsetGet(is,mid);if (value > cur) {min = mid+1;} else if (value < cur) {max = mid-1;} else {break;}}if (value == cur) {if (pos) *pos = mid;return 1;} else {if (pos) *pos = min;return 0;}
}
检测元素是否在集合中
检测操作非常简单,只是简单的调用intsetSearch方法
/* Determine whether a value belongs to this set */
uint8_t intsetFind(intset *is, int64_t value) {uint8_t valenc = _intsetValueEncoding(value);return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}
新增一个更大数值类型元素
如果一开始时,集合中保存的元素只有0x01,那么集合的类型是INTSET_ENC_INT16。contents数组的长度也是INTSET_ENC_INT16的长度。现在要往集合中新增一个元素0x12345678,这个时候INTSET_ENC_INT16类型长度的空间已经不能保存该数据了。于是需要对整个结构进行升级
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {uint8_t curenc = intrev32ifbe(is->encoding);uint8_t newenc = _intsetValueEncoding(value);int length = intrev32ifbe(is->length);
先要获取以前集合类型和长度,还要计算新集合为何种类型
int prepend = value < 0 ? 1 : 0;
然后检查新增的值是否为负数。因为该数值的绝对值比之前数组中所有元素都要大,所以如果该数如果是负数,则它比之前任何元素都小,这样它就要插在头部。相反,如果它是正数,则可能是插在尾部。
/* First set new encoding and resize */is->encoding = intrev32ifbe(newenc);is = intsetResize(is,intrev32ifbe(is->length)+1);/* Upgrade back-to-front so we don't overwrite values.* Note that the "prepend" variable is used to make sure we have an empty* space at either the beginning or the end of the intset. */while(length--)_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
使用新的类型更新集合encoding字段,再通过intsetResize重新分配集合的内存。因为当前内存数据分布和之前的一致(除了变长了),所以还要通过之后的while循环将之前的值转移到其现在应该在的位置。
/* Set the value at the beginning or the end. */if (prepend)_intsetSet(is,0,value);else_intsetSet(is,intrev32ifbe(is->length),value);is->length = intrev32ifbe(intrev32ifbe(is->length)+1);return is;
}
最后视新增数据的正负情况插入到新结构的不同位置。
数组尾部空间平移
这步操作在要往数组中间插入或者删除元素时发生。如果插入元素,则需要将插入位置的元素及之后的元素一起向后平移。如果删除元素,则要将被删除元素之后的元素向前平移
static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {void *src, *dst;uint32_t bytes = intrev32ifbe(is->length)-from;uint32_t encoding = intrev32ifbe(is->encoding);if (encoding == INTSET_ENC_INT64) {src = (int64_t*)is->contents+from;dst = (int64_t*)is->contents+to;bytes *= sizeof(int64_t);} else if (encoding == INTSET_ENC_INT32) {src = (int32_t*)is->contents+from;dst = (int32_t*)is->contents+to;bytes *= sizeof(int32_t);} else {src = (int16_t*)is->contents+from;dst = (int16_t*)is->contents+to;bytes *= sizeof(int16_t);}memmove(dst,src,bytes);
}
增加元素
增加元素时,要先判断待添加的元素是否比现在的集合类型大。如果是,则要重新分配和更新整个集合内存空间
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {uint8_t valenc = _intsetValueEncoding(value);uint32_t pos;if (success) *success = 1;/* Upgrade encoding if necessary. If we need to upgrade, we know that* this value should be either appended (if > 0) or prepended (if < 0),* because it lies outside the range of existing values. */if (valenc > intrev32ifbe(is->encoding)) {/* This always succeeds, so we don't need to curry *success. */return intsetUpgradeAndAdd(is,value);}
如果之前集合的类型可以承载待添加的元素,则先去检查元素是否已经在数组中。如果已经存在,则不再进行添加操作,直接认为操作成功
else {/* Abort if the value is already present in the set.* This call will populate "pos" with the right position to insert* the value when it cannot be found. */if (intsetSearch(is,value,&pos)) {if (success) *success = 0;return is;}
如果不在数组中,则上面的intsetSearch方法将计算出待添加的数据需要被插入到数组中的的位置。这个时候就需要重新分配集合长度,并将要插入的位置及之后的数据向后平移,并把待添加数据设置到数组的相应位置。
is = intsetResize(is,intrev32ifbe(is->length)+1);if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);}_intsetSet(is,pos,value);is->length = intrev32ifbe(intrev32ifbe(is->length)+1);return is;
}
删除元素
删除元素比较简单,只要通过intsetSearch找到元素的位置,将该位置之后的元素向前平移就行了
/* Delete integer from intset */
intset *intsetRemove(intset *is, int64_t value, int *success) {uint8_t valenc = _intsetValueEncoding(value);uint32_t pos;if (success) *success = 0;if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {uint32_t len = intrev32ifbe(is->length);/* We know we can delete */if (success) *success = 1;/* Overwrite value with tail and update length */if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);is = intsetResize(is,len-1);is->length = intrev32ifbe(len-1);}return is;
}
通过上面的函数,我们发现Redis的整数集在增删改元素时要自动调整元素排序。在新增绝对值超过当前集合可以表达的数据时,升级当前集合。但是如果删除元素时,即使现存的数字都比当前集合表达的区间的最小值还要小,也不会发生降级的操作。
相关文章:
GitHub标星1.2w+,Chrome最天秀的插件都在这里
作者 | Rocky0429来源 | Python空间(ID: Devtogether)大家好,我是 Rocky0429,一个沉迷 Chrome 不能自拔的蒟蒻...作为一个在远古时代用过什么 IE、360、猎豹等浏览器的资深器哥,当我第一次了解 Chrome 的时候ÿ…

基础篇 第四节 项目进度计划编辑 之 任务关联性设定
1.任务关联性的类型 ◎完成 —— 开始 FS ◎开始 —— 开始 SS ◎开始 —— 完成 SF 完成 —— 完成 FF 2.设定任务关联性 三种方法: ◎在条形图中直接拖拽 ◎在“前置任务”列中编辑 ◎在“任务信息”中的“前置任务”选项卡中编辑 3.设定“延隔时间” 延隔时间小于…
开坑,写点Polymer 1.0 教程第3篇——组件注册与创建
之前一篇算是带大家大致领略了一下Polymer的风采。这篇我们稍微深入一丢丢,讲下组件的注册和创建。 创建自定义组件的几种方式 这里我们使用Polymer函数注册了一个自定义组件"my-element" // register an element Polymer({is: my-element,created: funct…
Redis源码解析——Zipmap
本文介绍的是Redis中Zipmap的原理和实现。(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结构包含一个头信息、一系列字符串对(之后把一个“字符串对”称为一个“元素…

IIS7入门之旅:(3)CGI application和FastCGI application的区别
前言: 一如既往地,IIS支持通过提供pluggable module来提供对第3方script的支持,例如php等。在IIS7中,对于CGI的支持有了一个新的变化,就是同时提供了2种CGI支持模块,分别为:CGIModule (cgi.dll)和FastCGIMo…

抗击疫情!阿里云为加速新药疫苗研发提供免费AI算力
1月29日,阿里云正式宣布:疫情期间,向全球公共科研机构免费开放一切AI算力,以加速本次新型肺炎新药和疫苗研发。 目前,中国疾控中心已成功分离病毒,疫苗研发和药物筛选仍在争分夺秒地进行。新药和疫苗研发期…
SpriteBuilder中如何平均拉伸精灵帧动画的距离
首先要在Timeline中选中所有的精灵帧,可以通过如下2种的任意一种办法达成: 1按下Shift键的同时鼠标单击它们 2鼠标在Timeline空白区拖拽直到拉出的矩形包围住所有精灵帧方块后放开鼠标。 你可以通过查看Timeline中精灵帧方块上是否有阴影来辨识是否选中…

C++拾趣——类构造函数的隐式转换
之前看过一些批判C的文章,大致意思是它包含了太多的“奇技淫巧”,并不是一门好的语言。我对这个“奇技淫巧”的描述颇感兴趣,因为按照批判者的说法,C的一些特性恰巧可以让一些炫耀技术的同学有了炫耀的资本——毕竟路人皆知的东西…
数十名工程师作战5天,阿里达摩院连夜研发智能疫情机器人
作者 | Just出品 | AI科技大本营(ID:rgznai100)新型肺炎疫情防控战在各大互联网科技公司拉响,阿里、百度等公司陆续对外提供相应技术和产品。当前,疫情当前防控一线人员紧缺,多地政务热线迎来大波问询市民,…

路由器互联端口处于不同网段的路由方法和原理
如下图:两台cisco路由器相连接的两个端口不在同一个网络,如何实现两台路由器的互联?初看似乎绝对不可能,但是这是可行的,而且我已经把这个变成了现实。这里讲述配置的方法,以及解释原理。这个就要讲到路由原…

上网行为管理产品选型简单考量
信息化浪潮汹涌向前,人们的生活、工作、学习越来越离不开IT,离不开网络。 对于很多组织来讲,网络就意味着效率、甚至生产力,协同办公、决策、科研、资金划拨等等都对网络有了前所未有的依赖。网络应用日益复杂、多变、动态特征发展…
码农技术炒股之路——配置管理器、日志管理器
配置管理器和日志管理器是项目中最为独立的模块。我们可以很方便将其剥离出来供其他Python工程使用。文件的重点将是介绍Python单例和logging模块的使用。(转载请指明出于breaksoftware的csdn博客) 配置管理器 在《码农技术炒股之路——架构和设计》中我…
“数学不好,干啥都不行!”资深程序员:别再瞎努力了!
之前很多程序员读者向我们反馈:1)做算法优化时,只能现搬书里的算法,遇到不一样的问题,就不会了。2)面试一旦涉及到算法和数据结构,如果数学不行,面试基本就凉凉了。3)算法…

受限列表 队列与栈
2019独角兽企业重金招聘Python工程师标准>>> 队列与栈为受限列表,队列为先入先出型列表,而栈为先入后出型列表,有关列表的实现可以查看 http://my.oschina.net/u/2011113/blog/514713 。 结构图为 Queue实现了IQueue接口ÿ…
码农技术炒股之路——数据库管理器、正则表达式管理器
我选用的数据库是Mysql。选用它是因为其可以满足我的需求,而且资料多。因为作为第三方工具,难免有一些配置问题。所以本文也会讲解一些和Mysql配置及开发相关的问题。(转载请指明出于breaksoftware的csdn博客) 数据库管理器 Mysq…

Overview of ISA and TMG Networking and ISA Networking Case Study (Part 1)
老方说:此篇文章摘自ISASERVER.ORG网站,出自Thomas Shinder达人之手。严重建议ISA爱好者看看。Published: Dec 16, 2008 Updated: Jan 21, 2009 Author: Thomas Shinder What ISA/TMG firewall Networks are about and how the firewall uses these ne…
阿里云免费开放一切AI算力,加速新型冠状病毒新药和疫苗研发
近日,阿里云宣布,为了帮助加速新药和疫苗研发,将向全球公共科研机构免费开放一切AI算力。目前,中国疾控中心已成功分离病毒,疫苗研发和药物筛选仍在争分夺秒地进行。新药和疫苗研发期间,需要进行大量的数据…

ASP.net(C#)批量上传图片(完整版)
来自:http://blog.itpub.net/9869521/viewspace-667955/ 这篇关于ASP.Net批量上传图片的文章写得非常好,偶尔在网上看到想转载到这里,却费劲了周折。为了更新这篇文章,我用了近半个小时,网上的转载都残缺不全ÿ…

码农技术炒股之路——任务管理器
系统任务和普通任务都是通过任务管理器调度的。它们的区别是:系统任务在程序运行后即不会被修改,而普通任务则会被修改。(转载请指明出于breaksoftware的csdn博客) 为什么要有这样的设计?因为我希望它是一个可以不用停…
面对新型肺炎疫情,AI能做什么?
作者 | 马超出品 | AI科技大本营(ID:rgznai100)根据最新的新型冠状病毒疫情通报,截至1月30日24时,国家卫生健康委公布确诊病例9692例,重症病例1527例,累计死亡病例213例,另有疑似病例15238例。为…

大家帮忙.谢谢!..(急急急急急)
大家帮忙.谢谢!..(急急急急急) Delphi / Windows SDK/APIhttp://www.delphi2007.net/DelphiDB/html/delphi_20061218224617231.htmlprocedure TForm1.Button4Click(Sender: TObject); var P : pstring; i, j : integer; begin GetMem(p, sizeof(stri…

HDU4866 Shooting (要持久段树)
意甲冠军: 给你一些并行x行轴。总是询问坐标x的顶部之前,k一个段高度,。标题是必须在线。思路: 首先要会可持久化线段树(又称主席树和函数式线段树)。不会的能够去做下POJ 2104。 把全部线段高度离散化,作为结点建线段…
C++过去的这一年
作者 | Bartek译者 | 苏本如,责编 | 屠敏出品 | CSDN(ID:CSDNnews)【导读】本文旨在让我们回顾 C 2019年里的变化和发展!我们将重点关注本年度里 C 上发生的重大事件,标准的发展,工具的变化等等…
码农技术炒股之路——抓取股票基本信息、实时交易信息、主力动向信息
从本节开始,我们开始介绍各个抓取和备份业务。(转载请指明出于breaksoftware的csdn博客) 因为我们数据库很多,数据库中表也很多,所以我们需要一个自动检测并创建数据库和表的功能。在《码农技术炒股之路——数据库管理…

TemplateBuilder
http://msdn.microsoft.com/zh-cn/vstudio/system.web.ui.templatebuilder_members(VS.85).aspx TemplateBuilder 成员TemplateBuilder 成员支持在生成模板及其包含的子控件时使用的页分析器。 下表列出了由 TemplateBuilder 类型公开的成员。 公共构造函数 名称 说明 Templat…

【iOS UI】iOS 9 GUI 资源分享
分享的内容包括一个【DesignCode-iOS-9-GUI】Sketch 文件, 和苹果官方释出的【SF-UI、SF-Compact】两种字体的安装包。 以上内容是正版、免费的 <a href "https://itunes.apple.com/cn/app/sketch-3/id852320343?mt12">Sketch</a> 是收费软…
反向R?削弱显著特征为细粒度分类带来提升 | AAAI 2020
作者 | VincentLee来源 | 晓飞的算法工程笔记导读:论文提出了类似于dropout作用的diversification block,通过抑制特征图的高响应区域来反向提高模型的特征提取能力,在损失函数方面,提出专注于top-k类别的gradient-boosting loss来…

C#初学——doWhile
继续上面的学习,这次的是流程控制,用dowhile,代码如下,还是用语言选择来作为事例的。using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication9 { class Program { static void Main(s…

码农技术炒股之路——实时交易信息、主力动向信息分库备份
一般来说,一个股票信息应该保存在一张表中。但是由于我机器资源限制,且我希望尽快频率的抓取数据。所以每天我将所有股票的实时交易信息放在daily_temp库中的一个以日期命名的表中。主力动向信息也是如此。但是盘后分析股票时,我们会以单只股…