Postgresql中JIT函数能否inline的依据function_inlinable
相关
《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》
《LLVM的ThinLTO编译优化技术在Postgresql中的应用》
前置阅读:《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》
在JIT inline函数的过程中,会通过函数的bc代码,经过一系列规则、成本的判断来决定函数能否Inline,本篇重点分析这段逻辑:function_inlinable。
总结速查:
- 入参F(llvm::Function):待inline函数
- 入参functionStates(数组):记录了表达式计算所需要的所有函数,在function_inlinable函数内部检查的过程中,函数调用的其他函数,能inline的也会被加到这个数组中。
- 入参worklist(数组):记录了待处理的
{函数名,搜索路径},包括本次表达式计算的函数 和 在function_inlinable函数内部检查的过程中,函数调用的其他函数。 - 入参visitedFunctions(llvm::Function的SET):处理过的函数名。
- 入参running_instcount:经过function_inlinable的dfs搜索,包括当前函数和所有被调用者的指令数的总和。
- 入参importVars(String SET ):全局变量 和 当前函数调用的其他函数的函数名,类似于符号表。
function_inlinable会做dfs搜索所有调用到的函数,关心函数的指令数、里面用到的全局变量的个数。
1 function_inlinable part 1
function_inlinable(...)
{...
- 弱定义函数,
__attribute__((weak)),不会Inline。
if (F.isInterposable())return false;
- 通常指的是C代码中有inline关键字的函数,不需要这里再inline了。
if (F.hasAvailableExternallyLinkage())return false;
- 把函数从IR文件加载到内存中使用。
if (F.materialize())elog(FATAL, "failed to materialize metadata");
- 确定函数没有NoInline属性(后文有个例子)。
if (F.getAttributes().hasFnAttr(llvm::Attribute::NoInline)){ilog(DEBUG1, "ineligibile to import %s due to noinline",F.getName().data());return false;}
- function_references目的是为了了解当前函数引用了哪些变量和其他函数,评估它的大致复杂度。
- 这里以 dexp函数为例展开讲下function_references的流程:
function_references(F, running_instcount, referencedVars, referencedFunctions);
2 function_references
2.1 基础知识
- BasicBlock 表示的是基本块类,Arugument 表示的是函数的形参,Constant 表示的是形如 i32 4 的常量,Instruction 表示的是形如
add i32 %a,%b的指令。 - Value 是一个非常基础的基类,一个继承于 Value 的子类表示它的结果可以被其他地方使用。
- User代表了任何可以拥有操作数的LLVM对象。例如
%1 = add i32 %a, %b是Instruction,同时也是一个User,抽象理解就是拥有操作数的一切对象都是User。

2.2 dexp的ir
定义:
; Function Attrs: nounwind uwtable
define dso_local i64 @dexp(ptr nocapture noundef readonly %0) local_unnamed_addr #6 {%2 = getelementptr inbounds %struct.FunctionCallInfoBaseData, ptr %0, i64 0, i32 6, i64 0, i32 0%3 = bitcast ptr %2 to ptr%4 = load double, ptr %3, align 8%5 = fcmp uno double %4, 0.000000e+00br i1 %5, label %28, label %66: ; preds = %1%7 = tail call double @llvm.fabs.f64(double %4) #22%8 = fcmp oeq double %7, 0x7FF0000000000000br i1 %8, label %9, label %129: ; preds = %6%10 = fcmp ogt double %4, 0.000000e+00%11 = select i1 %10, double %4, double 0.000000e+00br label %2812: ; preds = %6%13 = tail call ptr @__errno_location() #23store i32 0, ptr %13, align 4%14 = tail call double @exp(double noundef %4) #20%15 = load i32, ptr %13, align 4%16 = icmp eq i32 %15, 34br i1 %16, label %17, label %21, !prof !1117: ; preds = %12%18 = fcmp une double %14, 0.000000e+00br i1 %18, label %19, label %2019: ; preds = %17tail call void @float_overflow_error() #24unreachable20: ; preds = %17tail call void @float_underflow_error() #24unreachable21: ; preds = %12%22 = tail call double @llvm.fabs.f64(double %14) #22%23 = fcmp oeq double %22, 0x7FF0000000000000br i1 %23, label %24, label %25, !prof !1124: ; preds = %21tail call void @float_overflow_error() #24unreachable25: ; preds = %21%26 = fcmp oeq double %14, 0.000000e+00br i1 %26, label %27, label %28, !prof !1127: ; preds = %25tail call void @float_underflow_error() #24unreachable28: ; preds = %25, %9, %1%29 = phi double [ %11, %9 ], [ %14, %25 ], [ %4, %1 ]%30 = bitcast double %29 to i64ret i64 %30
}
2.3 function_references函数
static void
function_references(llvm::Function &F,int &running_instcount,llvm::SmallPtrSet<llvm::GlobalVariable *, 8> &referencedVars,llvm::SmallPtrSet<llvm::Function *, 8> &referencedFunctions)
{
- 申请32个位置的Set存放User指针,具体就是Instruction
llvm::SmallPtrSet<const llvm::User *, 32> Visited;for (llvm::BasicBlock &BB : F){for (llvm::Instruction &I : BB){if (llvm::isa<llvm::DbgInfoIntrinsic>(I))continue;
- 申请8个位置的vector存放llvm::User指针(Instruction的基类):
llvm::SmallVector<llvm::User *, 8> Worklist;Worklist.push_back(&I);
- 指令计数running_instcount(Instruction的基类):
running_instcount++;while (!Worklist.empty()) {llvm::User *U = Worklist.pop_back_val();
- 这条指令之前有没有被记录过:
if (!Visited.insert(U).second)continue;
- 遍历Instruction的操作数operands,操作数的基类也是User:
for (auto &OI : U->operands()) {llvm::User *Operand = llvm::dyn_cast<llvm::User>(OI);if (!Operand)continue;
- 当前拿到的操作数是一个baseblock的地址,一般是用于跳转,不需要记录:
if (llvm::isa<llvm::BlockAddress>(Operand))continue;
- 这里看到一个全局变量,需要记录到referencedVars中,并把全局变量的定义拿出来,放到Worklist里面去统计一把,比如一个全局变量定义为int a = 1,那么这一个Instruction会在下一轮循环中被统计。
if (auto *GV = llvm::dyn_cast<llvm::GlobalVariable>(Operand)) {referencedVars.insert(GV);if (GV->hasInitializer())Worklist.push_back(GV->getInitializer());continue;}
- 这里发现一个操作数是另一个函数,说明有其他函数引用,将Function指针记录到referencedFunctions中。
if (auto *CF = llvm::dyn_cast<llvm::Function>(Operand)) {referencedFunctions.insert(CF);continue;}Worklist.push_back(Operand);}}}}
}
执行结束后:
- running_instcount:35
- IR中有35个指令
- referencedVars:空
- referencedFunctions:5个函数
dexp函数的IR分两部分:函数摘要和函数定义(index文件就是收集了bc文件中的函数摘要)
摘要:
^62 = gv: (name: "dexp", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 35, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^302), (callee: ^157), (callee: ^277), (callee: ^54))))) ; guid = 3352526880228194314
定义
$ cat float.ll | grep -A 58 '@dexp'
define dso_local i64 @dexp(ptr nocapture noundef readonly %0) local_unnamed_addr #6 {%2 = getelementptr inbounds %struct.FunctionCallInfoBaseData, ptr %0, i64 0, i32 6, i64 0, i32 0%3 = bitcast ptr %2 to ptr%4 = load double, ptr %3, align 8%5 = fcmp uno double %4, 0.000000e+00br i1 %5, label %28, label %66: ; preds = %1%7 = tail call double @llvm.fabs.f64(double %4) #22%8 = fcmp oeq double %7, 0x7FF0000000000000br i1 %8, label %9, label %129: ; preds = %6%10 = fcmp ogt double %4, 0.000000e+00%11 = select i1 %10, double %4, double 0.000000e+00br label %2812: ; preds = %6%13 = tail call ptr @__errno_location() #23store i32 0, ptr %13, align 4%14 = tail call double @exp(double noundef %4) #20%15 = load i32, ptr %13, align 4%16 = icmp eq i32 %15, 34br i1 %16, label %17, label %21, !prof !1117: ; preds = %12%18 = fcmp une double %14, 0.000000e+00br i1 %18, label %19, label %2019: ; preds = %17tail call void @float_overflow_error() #24unreachable20: ; preds = %17tail call void @float_underflow_error() #24unreachable21: ; preds = %12%22 = tail call double @llvm.fabs.f64(double %14) #22%23 = fcmp oeq double %22, 0x7FF0000000000000br i1 %23, label %24, label %25, !prof !1124: ; preds = %21tail call void @float_overflow_error() #24unreachable25: ; preds = %21%26 = fcmp oeq double %14, 0.000000e+00br i1 %26, label %27, label %28, !prof !1127: ; preds = %25tail call void @float_underflow_error() #24unreachable28: ; preds = %25, %9, %1%29 = phi double [ %11, %9 ], [ %14, %25 ], [ %4, %1 ]%30 = bitcast double %29 to i64ret i64 %30
}
- 引用函数个数:去重后5个

- 指令个数:35

- 引用全局变量个数:0个
和function_references计算结果一致。
3 function_inlinable part 2
- 记录全局变量到importVars,并增加成本:
for (llvm::GlobalVariable* rv: referencedVars){...importVars.insert(rv->getName());/* small cost attributed to each cloned global */running_instcount += 5;}
- 标记当前函数已经处理过了:
visitedFunctions.insert(&F);
- 检查dexp调用的函数:这里会处理5个函数:
llvm.fabs.f64__errno_locationexpfloat_overflow_errorfloat_underflow_error
for (llvm::Function* referencedFunction: referencedFunctions){llvm::StringSet<> recImportVars;if (referencedFunction->materialize())elog(FATAL, "failed to materialize metadata");
- 判断是不是llvm内建函数,例如循环给数组赋零有可能被clang在-O2时被优化为llvm.memset
- dexp调用的五个函数中,只有llvm.fabs.f64是llvm内建函数:
if (referencedFunction->isIntrinsic())continue;
- 已经处理过了?
if (!visitedFunctions.insert(referencedFunction).second)continue;
- 当前函数在其他编译单元?
- 例如
__errno_location函数就在glibc中。
if (referencedFunction->hasExternalLinkage()){llvm::StringRef funcName = referencedFunction->getName();/** Don't bother checking for inlining if remaining cost budget is* very small.*/
- inline_initial_cost默认给150。
- subThreshold = inline_initial_cost * inline_cost_decay_factor = 150 * 0.5 = 75
if (subThreshold < 5)continue;auto it = functionStates.find(funcName);if (it == functionStates.end()){
- 注意functionStates数组里面包含本次表达式计算用到的所有函数,比如int4abs、dexp、slot_getsomeattrs_int、i4tod等等。
- 这里会把需要inline的函数加到functionStates中,先不做其他处理。
FunctionInlineState inlineState;inlineState.costLimit = subThreshold;inlineState.processed = false;inlineState.inlined = false;inlineState.allowReconsidering = false;functionStates[funcName] = inlineState;worklist.push_back({funcName, searchpath});ilog(DEBUG1,"considering extern function %s at %d for inlining",funcName.data(), subThreshold);}...
- 弱定义函数,
__attribute__((weak)),排除。
if (referencedFunction->isInterposable())return false;
- 递归调用function_inlinable,检查内层函数。
if (!function_inlinable(*referencedFunction,subThreshold,functionStates,worklist,searchpath,visitedFunctions,running_instcount,recImportVars)){return false;}/* import referenced function itself */importVars.insert(referencedFunction->getName());/* import referenced function and its dependents */for (auto& recImportVar : recImportVars)importVars.insert(recImportVar.first());}
经过function_inlinable的递归调用,dfs所有会调用到的函数,最终:
- 需要inline的函数已经都加入到functionStates中。
- 需要Inline的
{函数名字,搜索路径}在worklist中。 - 函数名和全局变量名,全部加入到worklist。
返回true表示当前函数可以inline。
return true;
}
4 其他
dexp
怎么拿到函数的guid:funcGUID = llvm::GlobalValue::getGUID(cfuncname);
(GUID是用函数名MD5 hash出来的)
funcGUID = 3352526880228194314
index文件中查看函数属性:
^12463 = gv: (guid: 3352526880228194314, summaries: (function: (module: ^604, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 79, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^6190), (callee: ^59633), (callee: ^10786), (callee: ^32543)))))
这里函数被标记了noInline: 1,所以该函数不会被inline。
但是dexp为什么不能被inline呢?看起来函数不长,分支也不多,也没有标记__attribute__((noinline))。
Datum
dexp(PG_FUNCTION_ARGS)
{float8 arg1 = PG_GETARG_FLOAT8(0);float8 result;if (isnan(arg1))result = arg1;else if (isinf(arg1)){/* Per POSIX, exp(-Inf) is 0 */result = (arg1 > 0.0) ? arg1 : 0;}else{errno = 0;result = exp(arg1);if (unlikely(errno == ERANGE)){if (result != 0.0)float_overflow_error();elsefloat_underflow_error();}else if (unlikely(isinf(result)))float_overflow_error();else if (unlikely(result == 0.0))float_underflow_error();}PG_RETURN_FLOAT8(result);
}
原因是这里llvm是按O2编译的,按O0编译后noInline: 0
^10363 = gv: (guid: 3352526880228194314, summaries: (function: (module: ^604, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 35, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^49065), (callee: ^8990)))))
相关文章:
Postgresql中JIT函数能否inline的依据function_inlinable
相关 《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》 《LLVM的ThinLTO编译优化技术在Postgresql中的应用》 前置阅读:《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》 在JIT inline函数的过…...
存储过程作为初始化数据例子
查询出每个人员,为每个人员插入11条数据,作为初始化数据 oracle存储过程 CREATE OR REPLACE PROCEDURE initialize_order_warn_config ISv_id NUMBER; BEGINSELECT COALESCE(MAX(id), 0) INTO v_id FROM order_warn_config;FOR rec IN (SELECT DISTINCT…...
【数据分析】 JupyterNotebook安装及使用简介
各位大佬好 ,这里是阿川的博客 , 祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 在数据分析中,一般用Pycharm编辑代…...
nginx命令大全
以下是Nginx的一些常用命令,适用于Linux环境,部分命令在Windows系统下也适用,但命令形式可能有所不同: 查看Nginx版本号: nginx -v:简短显示版本号。nginx -V:详细显示版本号及编译配置信息。 启动Nginx…...
【数据结构】顺序表与链表的差异
顺序表和链表都是线性表,它们有着相似的部分,但是同时也有着很大的差异。 存储空间上的差异: 对于插入上的不同点,顺序表在空间不够时需要扩容,而如果在使用realloc函数去扩容,会有原地扩容和异地扩容两种情…...
小程序如何进行评分评价
小程序以其便捷、快速、无需安装的特点,成为了众多企业、品牌与消费者之间的重要连接桥梁。而评价评分机制,作为小程序中不可或缺的一环,对于提升用户体验、建立用户信任、促进商家与用户的互动等方面,都具有至关重要的意义。本文…...
【MATLAB源码-第206期】基于matlab的差分进化算法(DE)机器人栅格路径规划,输出做短路径图和适应度曲线。
操作环境: MATLAB 2022a 1、算法描述 差分进化算法(Differential Evolution, DE)是一种有效的实数编码的进化算法,主要用于解决实值函数的全局优化问题。本文将详细介绍差分进化算法的背景、原理、操作步骤、参数选择以及实际应…...
Python图形界面(GUI)Tkinter笔记(三):控件的定位(1)
Tkinter(GUI)设计图形界面时有三种控件的包装方法去定位各控件在窗口(父容器、根窗口)上的位置。 【1】pack()方法:用方位来定位位置,类似于Word文档中的文字对齐方式。 【2】grid()方法:用二维表格式的坐标值定位,类似于EXCEL单位元。 【3】place()方法:用窗口的像…...
数据结构--单链表 详解(附代码
目录: 1:链表的概念及结构 2:实现单链表 3:常见疑问 解答 (看到最后!!) 一:链表的概念及结构 1.1 概念: 链表是⼀种 物理存储结构上非连续、非顺序的 存储结…...
leetcode 1749.任意子数组和的绝对值的最大值
思路:dp 说到绝对值,大家肯定不陌生,但是用在dp上就会使问题变得稍微复杂一些了。 我们在最大子数组和的那道题中知道,在状态转移的时候,我们会舍弃掉为负数的连续部分,重新构建连续的子串。但是…...
Linux进程——进程地址空间
前言:在讲完环境变量后,相信大家对Linux有更进一步的认识,而Linux进程概念到这也快接近尾声了,现在我们了解Linux进程中的地址空间! 本篇主要内容: 了解程序地址空间 理解进程地址空间 探究页表和虚拟地址空…...
基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (三)
基于 LlaMA 3 LangGraph 在windows本地部署大模型 (三) 大家继续看 https://lilianweng.github.io/posts/2023-06-23-agent/的文档内容 第二部分:内存 记忆的类型 记忆可以定义为用于获取、存储、保留以及随后检索信息的过程。人脑中有多…...
python3如何安装bs4
在python官网找到beautifulsoup模块的下载页面,点击"downloap"将该模块的安装包下载到本地。 将该安装包解压,然后在打开cmd,并通过cmd进入到该安装包解压后的文件夹目录下。 在该文件目录下输入"python install setup.py&quo…...
docker容器技术篇:rancher管理平台部署kubernetes集群
rancher管理平台部署kubernetes集群 Rancher 是一个 Kubernetes 管理工具,让你能在任何地方和任何提供商上部署和运行集群。 Rancher 可以创建来自 Kubernetes 托管服务提供商的集群,创建节点并安装 Kubernetes,或者导入在任何地方运行的现…...
【计算机网络原理】初识网络原理和一些名词解释
˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…...
车载电子电器架构 —— 关于bus off汇总
车载电子电器架构 —— 关于bus off汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…...
Linux函数
目录 一、脚本函数 1.1 创建函数 1.2 使用函数 二、函数返回值 2.1 默认的退出状态码 2.2 使用return命令 2.3 使用函数输出 三、在函数中使用变量 3.1 向函数传达参数 3.2 在函数中处理变量 四、数组变量和函数 4.1 向函数中传递数组 4.2 从函数中返回数组 五、函数…...
如何查看centos7中Java在哪些路径下
在 CentOS 7 上,你可以通过几种方式查找安装的 Java 版本及其路径。以下是一些常用的方法: 1. 使用 alternatives 命令 CentOS 使用 alternatives 系统来管理同一命令的多个版本。你可以使用以下命令来查看系统上所有 Java 安装的配置: su…...
信息安全-古典密码学简介
目录 C. D. Shannon: 一、置换密码 二、单表代替密码 ① 加法密码 ② 乘法密码 ③密钥词组代替密码 三、多表代替密码 代数密码 四、古典密码的穷举分析 1、单表代替密码分析 五、古典密码的统计分析 1、密钥词组单表代替密码的统计分析 2、英语的统计规…...
面试题 01.05. 一次编辑
字符串有三种编辑操作:插入一个英文字符、删除一个英文字符或者替换一个英文字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。 示例 1: 输入: first "pale" second "ple" 输出: True示例 2: 输入: first &qu…...
DHCP实验1
一、实验拓扑二、实验需求 1.PC1和PC2使用路由器模拟2.PC1和R1的g0/0口连接到SW的vlan10;PC2和R1的g0/1口连接到SW的vlan203.R1在vlan10的IP地址为192.168.1.1/24,vlan20的IP地址为192.168.2.1/244.在R1上配置DHCP服务,分别为2个网段分配IP地…...
4大场景解决散热难题:开源散热管理工具全攻略
4大场景解决散热难题:开源散热管理工具全攻略 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/FanCont…...
Quartus中生成与烧录FPGA板载Flash的jic文件全流程解析
1. 为什么需要jic文件? 刚接触FPGA开发的朋友可能会疑惑:为什么编译生成的sof文件不能直接烧录到Flash?这个问题要从FPGA的特性说起。FPGA芯片内部是基于SRAM结构的,这意味着每次断电后配置数据都会丢失。想象一下你正在用电脑写文…...
NaViL-9B图文理解入门:支持中英文混合提问的实测案例
NaViL-9B图文理解入门:支持中英文混合提问的实测案例 1. 认识NaViL-9B NaViL-9B是一款原生多模态大语言模型,由专业研究机构开发。它最大的特点是能够同时处理文字和图片信息,就像一个能"看图说话"的智能助手。无论是纯文字问题&…...
科研党福音!爱毕业aibye力荐6大AI论文平台,智能改写+降重功能全解析。
工具名称 核心功能 特色优势 Aibiye 论文生成降AI率 全学科覆盖、仿写优化、自动图表生成 Aicheck AI检测文献综述辅助 精准查新、3分钟高效成文 GPT学术版 润色/翻译/代码解释 多模型协同、PDF深度解析 摆平论文 大纲生成降重改写 三步出稿、本硕博通用 QuillB…...
Informer实战指南:从ProbSparse自注意力到生成式解码器的长序列预测优化
1. Informer模型的核心突破:为什么比Transformer更适合长序列预测? 第一次看到Informer论文时,最让我惊讶的是它在AAAI 2021上击败了众多Transformer变体获得最佳论文。这个专为长序列预测(Long Sequence Time-series Forecasting…...
OpenClaw任务编排:用Qwen3.5-4B-Claude实现爬虫+分析闭环
OpenClaw任务编排:用Qwen3.5-4B-Claude实现爬虫分析闭环 1. 为什么需要自动化任务编排 去年我接手了一个市场调研项目,需要每周从20多个网站抓取产品价格数据,清洗后生成趋势图表。最初用Python脚本手动Excel处理,每次要花3小时…...
工业自动化实战:三大品牌伺服驱动器IO与串口引脚接线全解析
1. 伺服驱动器接线基础:为什么IO与串口引脚如此重要 第一次接触伺服驱动器时,我被密密麻麻的接线端子吓到了。后来才发现,只要理解几个核心引脚的功能,剩下的都是举一反三。伺服驱动器的IO和串口引脚就像机器的"神经系统&quo…...
SAS(Serial Attached SCSI)在企业级存储中的核心设计与实战解析
1. SAS技术在企业级存储中的核心价值 如果你拆开过企业级存储设备,大概率会看到那些带着蓝色或黑色连接器的硬盘背板——这就是SAS技术的战场。作为存储架构师,我经手过的全闪存阵列和磁盘柜里,90%的核心连接都依赖SAS协议。和消费级SATA相比…...
ColorMemLCD电子纸驱动库:面向LPM013M126A的嵌入式低功耗显示方案
1. ColorMemLCD 库概述ColorMemLCD 是一款专为 JDI(Japan Display Inc.)LPM013M126A 型彩色内存式 LCD 显示模块设计的嵌入式图形驱动库。该库并非从零构建,而是继承自 ARM mbed OS 生态中广泛使用的GraphicDisplay抽象基类,延续了…...
