SGI 空间配置器

news/2024/5/3 13:24:53/文章来源:https://blog.csdn.net/qq_40080842/article/details/129170988

前言

空间配置器是 STL 六大组件之一,它总是隐藏在容器的背后,默默工作,默默付出。本文为《STL 源码剖析》读书笔记,主要讨论 SGI 版本空间的配置和释放,对代码进行解读时会改变一些写法,使其更易于阅读。

对象构造前的空间配置和对象析构后的空间释放,由 <stl_alloc.h> 负责,SGI 对此的设计哲学如下:

  • 向系统堆申请空间
  • 考虑多线程状态(本文不考虑多线程情况)
  • 考虑内存不足时的应变措施
  • 考虑小块内存过多造成的内存碎片问题

一级配置器

一级配置器并没有什么特殊的地方,就是调用 malloc() 和 free() 申请和释放内存。

__malloc_alloc_template 源码:

#if 0
#   include <new>
#   define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
#   include <iostream.h>
#   define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endiftemplate <int inst>
class __malloc_alloc_template {private:static void *oom_malloc(size_t);static void *oom_realloc(void *, size_t);#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUGstatic void (* __malloc_alloc_oom_handler)();
#endifpublic:static void * allocate(size_t n)
{void *result = malloc(n);if (0 == result) result = oom_malloc(n);return result;
}static void deallocate(void *p, size_t /* n */)
{free(p);
}static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{void * result = realloc(p, new_sz);if (0 == result) result = oom_realloc(p, new_sz);return result;
}static void (* set_malloc_handler(void (*f)()))()
{void (* old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return(old);
}};// malloc_alloc out-of-memory handling#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endiftemplate <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{void (* my_malloc_handler)();void *result;for (;;) {my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }(*my_malloc_handler)();result = malloc(n);if (result) return(result);}
}template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{void (* my_malloc_handler)();void *result;for (;;) {my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }(*my_malloc_handler)();result = realloc(p, n);if (result) return(result);}
}typedef __malloc_alloc_template<0> malloc_alloc;

oom

什么是 oom(out of member)?

申请内存时,如果没有空闲的物理内存,那么内核就会开始进行回收内存的工作。如果内存回收后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM (Out of Memory)机制

OOM 机制会根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存依然不足,OOM 会继续杀死占用物理内存较高的进程,直到释放足够的内存。

SGI 中的 __malloc_alloc_oom_handler 操作又是什么?

__malloc_alloc_oom_handler 指向内存不足时的处理函数,是 SGI 模仿 C++ 的 set_new_handler,因为没有使用 ::operator new 来分配内存,所以不能直接使用 set_new_handler。

一个设计良好的 new_handler 函数做以下事情:

  • 让更多内存可以被使用
  • 安装另一个 new_handler
  • 卸载 new_handler
  • 抛出 bad_alloc
  • 不返回

SGI 中内存不足时调用 oom_malloc() 和 oom_realloc(),在它们内部不断调用「内存不足处理函数」,期望在某次调用后,就得到了足够的内存然后返回。但如果「内存不足处理函数」并没有被客户端设定,便会调用 __THROW_BAD_ALLOC,丢出异常信息并终止进程。

内存不足时的处理操作:

// 此处的条件编译一定会执行 elif 部分
// 最后尽力了也申请不到内存时,就打印错误语句,结束程序
#if 0
#   include <new>
#   define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
#   include <iostream.h>
#   define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endif// 成员变量,指向内存不足时的处理函数,初始值为空
static void (* __malloc_alloc_oom_handler)();// 参数为新设置的内存不足处理函数
// 返回值为旧的内存不足处理函数
static auto set_malloc_handler(void (*f)()) -> void (*)() {void (* old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return(old);
}// 该非类型模板参数没有用处
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {void (* my_malloc_handler)();void *result;		// 指向申请到的内存while(true) {// 获取内存不足时的处理函数my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) {	// 如果没有设置处理函数,终止进程__THROW_BAD_ALLOC; }(*my_malloc_handler)();			// 调用内存不足处理函数result = malloc(n);				// 再次尝试申请if (result != nullptr) {return(result);				// 申请成功返回}}
}template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) {void (* my_malloc_handler)();void *result;while(true) {// 获取内存不足时的处理函数my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler) {	// 如果没有设置处理函数,终止进程__THROW_BAD_ALLOC; }(*my_malloc_handler)();			// 调用内存不足处理函数result = realloc(p, n);			// 再次尝试申请if (result != nullptr) {return(result);				// 申请成功返回}}
}

申请内存

一级配置器申请内存直接调用 malloc() 和 realloc() 函数。

static void * allocate(size_t n) {void *result = malloc(n);if (0 == result) {result = oom_malloc(n);				// 申请失败时调用 oom_malloc}return result;
}static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) {void * result = realloc(p, new_sz);if (0 == result) {result = oom_realloc(p, new_sz);	// 申请失败时调用 oom_realloc}return result;
}

释放内存

一级配置器释放内存直接调用 free() 函数。

// 第二个参数没有作用
static void deallocate(void *p, size_t /* n */) {free(p);
}

二级配置器

二级配置器就比一级配置器复杂的多,大于 128 字节的申请调用一级配置器,小于 128 字节的内存使用自由链表数组分配。整个二级配置器共享一个内存池,内存不足时从内存池获取。提供函数从下层获取内存,并向自由链表中填充内存。

自由链表如下,提供以 8 为倍数的小块内存,小块内存的头部 4/8 字节指向下一空闲节点。

自由链表

__default_alloc_template 源码:

enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};template <bool threads, int inst>
class __default_alloc_template {private:static size_t ROUND_UP(size_t bytes) {return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));}
private:union obj {union obj * free_list_link;char client_data[1];    /* The client sees this.        */};
private:static obj * volatile free_list[__NFREELISTS]; static  size_t FREELIST_INDEX(size_t bytes) {return (((bytes) + __ALIGN-1)/__ALIGN - 1);}static void *refill(size_t n);static char *chunk_alloc(size_t size, int &nobjs);// Chunk allocation state.static char *start_free;static char *end_free;static size_t heap_size;public:/* n must be > 0      */static void * allocate(size_t n){obj * volatile * my_free_list;obj * result;if (n > (size_t) __MAX_BYTES) {return(malloc_alloc::allocate(n));}my_free_list = free_list + FREELIST_INDEX(n);result = *my_free_list;if (result == 0) {void *r = refill(ROUND_UP(n));return r;}*my_free_list = result -> free_list_link;return (result);}/* p may not be 0 */static void deallocate(void *p, size_t n){obj *q = (obj *)p;obj * volatile * my_free_list;if (n > (size_t) __MAX_BYTES) {malloc_alloc::deallocate(p, n);return;}my_free_list = free_list + FREELIST_INDEX(n);q -> free_list_link = *my_free_list;*my_free_list = q;}static void* reallocate(void *p, size_t old_sz, size_t new_sz){void * result;size_t copy_sz;if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {return(realloc(p, new_sz));}if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);result = allocate(new_sz);copy_sz = new_sz > old_sz? old_sz : new_sz;memcpy(result, p, copy_sz);deallocate(p, old_sz);return(result);}} ;template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{char * result;size_t total_bytes = size * nobjs;size_t bytes_left = end_free - start_free;if (bytes_left >= total_bytes) {result = start_free;start_free += total_bytes;return(result);} else if (bytes_left >= size) {nobjs = bytes_left/size;total_bytes = size * nobjs;result = start_free;start_free += total_bytes;return(result);} else {size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);// Try to make use of the left-over piece.if (bytes_left > 0) {obj * __VOLATILE * my_free_list =free_list + FREELIST_INDEX(bytes_left);((obj *)start_free) -> free_list_link = *my_free_list;*my_free_list = (obj *)start_free;}start_free = (char *)malloc(bytes_to_get);if (0 == start_free) {int i;obj * __VOLATILE * my_free_list, *p;// Try to make do with what we have.  That can't// hurt.  We do not try smaller requests, since that tends// to result in disaster on multi-process machines.for (i = size; i <= __MAX_BYTES; i += __ALIGN) {my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;if (0 != p) {*my_free_list = p -> free_list_link;start_free = (char *)p;end_free = start_free + i;return(chunk_alloc(size, nobjs));// Any leftover piece will eventually make it to the// right free list.}}end_free = 0;	// In case of exception.start_free = (char *)malloc_alloc::allocate(bytes_to_get);// This should either throw an// exception or remedy the situation.  Thus we assume it// succeeded.}heap_size += bytes_to_get;end_free = start_free + bytes_to_get;return(chunk_alloc(size, nobjs));}
}template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{int nobjs = 20;char * chunk = chunk_alloc(n, nobjs);obj * volatile * my_free_list;obj * result;obj * current_obj, * next_obj;int i;if (1 == nobjs) return(chunk);my_free_list = free_list + FREELIST_INDEX(n);/* Build free list in chunk */result = (obj *)chunk;*my_free_list = next_obj = (obj *)(chunk + n);for (i = 1; ; i++) {current_obj = next_obj;next_obj = (obj *)((char *)next_obj + n);if (nobjs - 1 == i) {current_obj -> free_list_link = 0;break;} else {current_obj -> free_list_link = next_obj;}}return(result);
}template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * __VOLATILE
__default_alloc_template<threads, inst> ::free_list[__NFREELISTS] = 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

成员变量

enum {__ALIGN = 8};								// 对齐数
enum {__MAX_BYTES = 128};						// 可以申请的最大字节数
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};		// 自由链表个数static obj * volatile free_list[__NFREELISTS];	// 自由链表数组
static char *start_free;						// 内存池空间起始位置
static char *end_free;							// 内存池空间结束位置
static size_t heap_size;						// 多开辟的堆大小

还有一个比较特殊的成员,自由链表的节点结构。

该联合体从第一个字段看:它可以被视为一个指针,指向下一节点。从第二个字段看:它可以被视为一个指针,指向实际的数据空间。

实际上该联合体并没有实际的作用,只是为了便于理解。申请的一块内存,在没被使用的时候,可以用其头部 4/8 字节指向下一空闲节点,不用维护另外的指针。

union obj {union obj * free_list_link;char client_data[1];    /* The client sees this.        */
};

工具部分

这部分提供内存对齐,获取在自由链表数组中下标的函数。

因为自由链表中提供以 8 为倍数的小块内存,因此需要将申请的内存对齐为 8 的倍数。

static size_t ROUND_UP(size_t bytes) {// (bytes) + __ALIGN - 1 保证向对齐数进一位// ~(__ALIGN - 1) 去掉低位的 1return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
}

同时也需要知道对应大小在自由链表数组中的下标,以边获取内存和归还内存。

static  size_t FREELIST_INDEX(size_t bytes) {// - 1 因为数组的下标从零开始// 等于 ROUND_UP(bytes) / _ALIGN - 1// 因为低位的数在除对齐数后没有影响return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
}

申请内存

申请内存时首先判断大小,大于 128 字节就调用一级配置器,小于 128 字节就去自由链表中获取,自由链表中没有内存就调用 refill() 填充内存。

static void * allocate(size_t n) {obj * volatile * my_free_list;	obj * result;					// 带回申请的内存// 大于 128 字节调用一级配置器if (n > (size_t) __MAX_BYTES) {return(malloc_alloc::allocate(n));}// 找到对应大小的自由链表my_free_list = free_list + FREELIST_INDEX(n);result = *my_free_list;if (result == 0) {// 自由链表中没有内存可用,为其填充内存void *r = refill(ROUND_UP(n));return r;}// 取出一个节点,调整 free_list 指向下一节点*my_free_list = result -> free_list_link;return (result);
}

自由链表2

释放内存

释放时同样需要先判断内存大小,大于 128 字节调用一级配置器释放,小于 128 字节还给自由链表。

// p 为要释放的首地址,n 为对象的大小
static void deallocate(void *p, size_t n) {obj *q = (obj *)p;obj * volatile * my_free_list;// 大于 128 字节调用一级配置器if (n > (size_t) __MAX_BYTES) {malloc_alloc::deallocate(p, n);return;}// 找到要插入的位置my_free_list = free_list + FREELIST_INDEX(n);// 将节点头插进去q -> free_list_link = *my_free_list;*my_free_list = q;
}

自由链表3

填充自由链表

当申请内存时发现自由链表中没有可用内存后,就调用 refill()。refill() 的作用是为指定自由链表填充内存,新的内存从内存池中获取,默认情况下是填充 20 个节点,但万一内存池中内存不足,获取的节点可能小于 20 个。

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n) {int nobjs = 20;							// 默认填充的个数// 从内存池获取内存,nobjs 为引用传参,带回实际申请到的个数char * chunk = chunk_alloc(n, nobjs);obj * volatile * my_free_list;obj * result;							// 返回使用的节点obj * current_obj, * next_obj;int i;									// 只申请到一个节点,将该节点直接返回,不用向 free_list 中新增节点if (1 == nobjs) {return(chunk);}// 调整 my_free_list 指向,指向要添加节点的自由链表my_free_list = free_list + FREELIST_INDEX(n);// 需要将多余的节点插入到自由链表中// 获取第一个节点,后续返回使用result = (obj *)chunk;// + n 指向第二个节点*my_free_list = (obj *)(chunk + n);next_obj = (obj *)(chunk + n);for (i = 1; ; ++i) {// 分别指向当前节点、下一节点current_obj = next_obj;// (char *)next_obj + n 取一个节点的大小next_obj = (obj *)((char *)next_obj + n);// 一共申请了 nobjs 个节点,需要插入 n - 1 个if (nobjs - 1 == i) {// 将最后一个插入的节点置空current_obj -> free_list_link = 0;break;} else {// 采用尾插的方式current_obj -> free_list_link = next_obj;}}return(result);
}

内存池

chunk_alloc() 首先检查内存池中是否有内存可用,如果没有就尝试调用 malloc() 申请内存,申请失败就去更大节点的自由链表中寻找内存。如果经过上述艰难的过程,还是没有获取到内存的话,就会调用一级配置器,祈祷「内存不足处理函数」有所作用。

template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs) {char * result;								// 结果指针size_t total_bytes = size * nobjs;			// 要申请的总字节数size_t bytes_left = end_free - start_free;  // 内存池剩余空间if (bytes_left >= total_bytes) {// 内存池剩余空间满足需求result = start_free;start_free += total_bytes;	// 将 start 向后移,表示内存已被使用return(result);} else if (bytes_left >= size) {// 内存池剩余空间不能满足要求,但足够一个以上的节点nobjs = bytes_left/size;	// 能带回的节点个数total_bytes = size * nobjs;	// 申请到的字节数result = start_free;start_free += total_bytes;	// 调整 startreturn(result);} else {// 内存池剩余空间连一个节点也不能满足// 申请成功后后取走 total_bytes 字节,剩余部分留在内存池size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);if (bytes_left > 0) {// 内存池中还有剩余内存,把它分配到合适的自由链表中// 申请是 8 的倍数,使用也是 8 的倍数,因此可以找到合适的位置obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);((obj *)start_free) -> free_list_link = *my_free_list;*my_free_list = (obj *)start_free;}// 调用 malloc 向堆申请内存start_free = (char *)malloc(bytes_to_get);if (0 == start_free) {// 申请内存失败int i;obj * volatile * my_free_list, *p;for (i = size; i <= __MAX_BYTES; i += __ALIGN) {// 检查配有更大节点的自由链表是否有空间可用// 例如 32 字节自由链表没有内存可用,可以去 40、48 等自由链表查看my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;if (0 != p) {// 更大的自由链表中有内存可用*my_free_list = p -> free_list_link;	// 弹出一个节点start_free = (char *)p;					// 将弹出的节点放入内存池中end_free = start_free + i;				// 调整内存池大小return(chunk_alloc(size, nobjs));		// 此时已经有内存了,递归调用自己获取}}// 此时内存池没有内存,malloc 失败,也没有更大的可用节点// 尝试调用一级配置器,看内存不足处理函数是否有办法end_free = 0;start_free = (char *)malloc_alloc::allocate(bytes_to_get);} // end of if (0 == start_free)heap_size += bytes_to_get;				// 随着次数而增大,不太理解含义end_free = start_free + bytes_to_get;	// 调整内存池大小return(chunk_alloc(size, nobjs));		// 此时已经有内存了,递归调用自己获取} // end of if (bytes_left >= total_bytes)
}

自由链表4

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_262313.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业急需:拥有一个属于自身的知识库!

如今&#xff0c;拥有知识库对任何企业来说都是绝对必要的。特别是在软件即服务方面。如果您真的希望您的 SaaS 业务取得成功&#xff0c;您需要从第一天开始构建知识库。为什么&#xff1f;首先&#xff0c;SaaS 公司有一个货币化模型&#xff0c;专注于他们的每月经常性收入 …

多传感器分布式融合算法——多传感器网络协同目标跟踪和定位

多传感器分布式融合算法 应用&#xff1a; 多传感器网络协同目标跟踪及定位 原创不易&#xff0c;路过的各位大佬请点个赞 主要讲解算法&#xff1a; 多传感器集中式融合算法/分布式融合算法/序贯融合算法 多速率多传感器异步融合算法 多传感器…

PHP程序员适合创业吗?

创业是一件自然而然的事&#xff0c;不需要人为选择。 只要你是一个努力能干主动的人&#xff0c;当你在一个行业深耕5年之后&#xff0c;就会发现人生发展的下一步就是创业。当然如果行业合适的话。 什么叫行业合适呢&#xff1f; 就是创业的成本并不那么高&#xff0c;不需…

js 实现 Logo(图片)根据图片后面的图片颜色而变化成相反的颜色【解决logo固定后 会出现与不同板块的颜色相同导致于看不清logo的情况】

效果展示&#xff1a; <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <meta http-equiv"X-UA-Compatible" content"ieedge"><style type"text/css…

Unreal Engine 虚幻引擎,性能分析,优化(二)

一、CPU 性能分析 如渲染线程中出现 CPU 受限&#xff0c;原因可能是绘制调用过多。这是一个常见问题&#xff0c;美术师通常会将绘制调用进行组合&#xff0c;从而减少消耗&#xff08;如&#xff1a;将多个墙壁组合为一个网格体&#xff09;。实际消耗存在于多个区域中&…

vue3路由守卫

文章目录路由守卫1.全局路由守卫2.组件内守卫3.路由独享守卫提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考路由守卫 全局守卫&#xff08;3个&#xff09; 路由独享守卫&#xff08;1个&#xff09; 组件的守卫&#xff08;3个&#xff09; 路由守卫的…

蓝牙运动耳机哪个好,比较好的运动蓝牙耳机

很多想选择蓝牙运动耳机的朋友都不知道应该如何选择&#xff0c;运动首先需要注意的就是耳机的防水能力以及耳机佩戴舒适度&#xff0c;在运动当中会排出大量的汗水&#xff0c;耳机防水等级做到越高&#xff0c;可以更好地保护耳机不受汗水浸湿&#xff0c;下面就分享五款适合…

《图机器学习》-Graph Neural Network

前言 回顾之前的Node Embedding&#xff1a; 将图中的节点嵌入到d维空间&#xff0c;并确保图中相似的节点能够嵌在一起。 即学习一个编码器ENCENCENC确保图的节点嵌入到embedding space依然能够描述原空间节点之间的相似性。 在Node Embedding中&#xff0c;我们需要设计&…

CSCode 配置一条龙 CPP/CC

下载 官⽹下载地址&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 下载太慢&#xff0c;推荐⽂章&#xff1a;解决VsCode下载慢问题_wang13679201813的博客-CSDN博客_vscode下载慢 安装 无脑下一步 推荐插件 免配置&#xff1a; 1. Remote - SSH - 远程…

骨传导耳机是怎么传声的,选择骨传导耳机的时候需要注意什么?

​骨传导耳机之所以能够成为当下最火的耳机&#xff0c;骨传导技术将声音转化为震动感&#xff0c;通过骨头进行传播&#xff0c;不会堵塞耳朵&#xff0c;就不会影响到周围环境音。这种技术也让骨传导耳机比传统入耳式耳机更安全&#xff0c;无需入耳式设计&#xff0c;避免了…

Interview系列 - 07 Java | 集合的快速失败和安全失败机制 | 迭代器类源码 | CopyOnWriteArrayList

文章目录1. 集合的快速失败 (fail-fast)1. 使用增强for遍历集合并使用ArrayList的 remove() 方法删除集合元素2. 使用 forEach 遍历集合并使用ArrayList的 remove() 方法删除集合元素3. 使用迭代器遍历集合并使用ArrayList的 remove() 方法删除集合元素4. 使用迭代器遍历集合并…

【离线数仓-6-数据仓库开发ODS层设计要点】

离线数仓-6-数据仓库开发ODS层设计要点离线数仓-6-数据仓库开发ODS层1.数据仓库开发ODS层设计要点2.ODS层用户行为日志表1.hive中复杂结构体复习1.array2.map3.struct 复杂结构4.嵌套格式2.hive中针对复杂结构字符串的练习1.针对ods层为json格式数据的练习2.用户行为日志表的设…

详解数据库基本概念

数据库&#xff08;DataBase 简称 DB&#xff09;&#xff1a;是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合数据库管理系统&#xff08;DataBase Management System 简称 DBMS&#xff09;&#xff1a;是一种操纵和管理数据库的大型软件&#xf…

获取Windows11开发环境及VirtualBox配置指南

今天我们来讲一讲Windows11开发环境的快速搭建&#xff0c;主要是通过Virtualbox虚拟机安装微软官方预先配置好的Windows11环境包&#xff0c;配置简单&#xff0c;开箱即用。 获取虚拟机打包镜像 微软官方提供了多个系统平台的Windows11虚拟机镜打包镜像&#xff0c;只需要导…

python--排序总结

1.快速排序 a.原理 快速排序的基本思想是在待排序的 n 个元素中任取一个元素&#xff08;通常取第一个元素&#xff09;作为基准&#xff0c;把该元素放人最终位置后&#xff0c;整个数据序列被基准分割成两个子序列&#xff0c;所有小于基准的元素放置在前子序列中&#xff0…

代码随想录算法训练营day40 | 动态规划 343. 整数拆分 96.不同的二叉搜索树

day40343. 整数拆分1、确定dp数组以及下标的含义2、确定递推公式3、dp数组如何初始化4、确定遍历顺序5、举例推导dp数组96.不同的二叉搜索树1、确定dp数组&#xff08;dp table&#xff09;以及下标的含义2、确定递推公式3、dp数组如何初始化4、确定遍历顺序5、举例推导dp数组3…

[译文] 基于PostGIS3.1 生成格网数据

根据格网进行数据统计与分析是一种常用的方法&#xff0c;相比自然地理边界与行政管理边界而言&#xff0c;使用格网有如下特点&#xff1a;每个格网之间地位相等&#xff0c;没有上下级之分。每个格网的面积都相等。相邻两个格网单元中心点之间距离相等。适用于将数据从“空间…

【Kubernetes 企业项目实战】09、Rancher 2.6 管理 k8s-v1.23 及以上版本高可用集群

目录 一、Rancher 介绍 1.1Rancher简介 1.2 Rancher 和 k8s 的区别 1.3 Rancher 企业使用案例 二、安装 Rancher 2.1 初始化环境 2.2 安装 Rancher 2.3 登录 Rancher 平台 三、通过 Rancher 管理已存在的 k8s 集群 3.1 配置 rancher 3.2 导入 k8s ​四、通过 Ranc…

【MySQL】5.7版本解压安装配置

前言 之所以使用解压版本&#xff0c;而不使用exe安装&#xff0c;因为exe的安装方式删除过于麻烦&#xff01;&#xff01;&#xff01; 如果安装MySQL过程中&#xff0c;出错了或者想重新在来一把&#xff0c;删除mysql服务即可 sc delete mysql # 删除已经安装好的Mysql&a…

ICASSP2023录用率有可靠度还不错的消息了

点击文末公众号卡片&#xff0c;找对地方&#xff0c;轻松参会 由于录用邮件没说录用率&#xff0c;导致大家都不知道录用率是多少。 据一位群友的反馈&#xff0c;其小老板是meta review。该群友原话“接受率应该是42%”。 ICASSP2023投稿量6000&#xff0c;在投稿量大涨的…