干货 | 携程百亿级缓存系统探索之路——本地缓存结构选型与内存压缩

PIC17CXXX软浮点库实现:IEEE 754标准在8位MCU上的软件模拟与优化 在嵌入式系统开发中,浮点数运算对于处理传感器数据、实现控制算法至关重要。IEEE 754标准定义了浮点数的表示与运算规则,为高精度计算提供了基础。在没有硬件浮点单元(FPU)的微控制器上,通过软件模拟实现浮点运算,即软浮点(Soft-float),成为扩展其数学处理能力的关键技术。其原理是将浮点数拆解为符号、指数、尾数域,利用处理器的整数运算单元(如ALU)执行位操作和算术运算来模拟浮点操作。这项技术的核心价值在于,能在资源受限的嵌入式平台上,为复杂算法(如滤波、坐标变换)提供必要的计算精度与动态范围,同时 阅读详情

作者简介

 

一十,携程资深后端开发工程师;振青,携程高级后端开发专家。

一、前言

携程酒店查询服务是酒店BU后端的核心服务,主要负责提供所有酒店动态数据计算的统一接口。在处理请求的过程中,需要使用到酒店基础属性信息、价格信息等多维度的数据信息。为了保证服务的响应性能,酒店查询服务对所有在请求过程中需要使用到的相关数据进行了缓存。随着携程酒店业务的发展,查询服务目前在保证数据最终一致性以及增量秒级更新延迟的情况下,在包括服务器本地内存以及Redis等多种介质上缓存了百亿级的数据。

本文将主要讨论酒店查询服务技术团队是如何在保证读取效率的前提下,针对存储在服务器本地的缓存数据进行存储结构选型以及优化的过程。

二、内存结构选型

为了寻找合适的内存结构以达到理想的效果,本节将详细探讨在通用数据查询场景下,不同类型的数据结构的可行性与优劣势。

2.1 缓存结构的基础需求

2.1.1 支持高性能读取

在大部分应用场景下,之所以需要在服务器内存中存储缓存数据,是因为请求处理过程中需要高频次读取各类数据。为了保证服务的响应性能,我们有的时候会选择将数据提前存储在本地内存,利用空间换取时间。酒店查询服务就有这样的需求:服务平均每次请求需要读取数据上千次,同时对响应时间也有着严格的要求。因此,在这种高频次访问缓存的场景下,对数据的查找性能便有着极高的要求。

在常见的数据结构中,数组和散列表都能提供O(1)的查询速度,是不考虑其他因素下最高性能的选择。查找复杂度为O(log2N)的树则其次,其查找速度和数据规模有关,一般只能在数据规模很小的场景下选用。

2.1.2 支持高更新频率

在实际应用场景下,生产环境的缓存数据必然有新鲜度要求。面对海量数据,高频度的数据更新几乎无可避免。特别是像价格这类数据,一方面更改频次极高,另一方面又必须保证新的增量数据可以在秒级内快速同步至缓存中。这就要求所使用的缓存数据结构必须支持高性能并发读写的场景。如果随意的使用锁机制或是线程不安全的存储结构都会可能导致一些预期外的问题与风险:

1)并发更改风险

众所周知,Java提供实现的最常用的散列表HashMap是非线程安全的数据结构。若直接使用该类作为缓存结构,则在并发读写时就可能会因为重新Hash而读到错误的数据,甚至在极端情况下产生死循环的问题。

2)滥用读写锁

在频繁并发更新与读取的场景下,错误的锁机制很有可能导致在高频次写入时直接卡死应用处理请求时的高频次读取,进而产生大量请求排队以及其他问题。

因此,高更新频率需求所带来的线程安全问题,导致大部分的基础数据结构都无法适用于存储生产缓存数据。在绝大部分情况下,都需要牺牲一部分性能选择线程安全的数据结构。当然,对于某些特殊场景,也可根据需要来设计定制化的结构与锁机制来达到更优的性能。

经过上面的简单分析后,我们可以暂时认为线程安全的数组和散列表是一个较优的用以承载缓存数据的结构。

2.2 低空间开销的结构选型

由于实际应用的内存都是有限的,因此在保障读写性能的同时,我们也需要思考如何降低缓存所消耗的内存资源。为了保证服务正常的响应请求,酒店查询服务需要在本地存储千万量级的数据,而缓存能够在虚拟机上使用的内存空间却非常有限。因此,除了对数据本身进行过滤等预处理之外,用以存储数据的通用结构的内存开销也要尽可能的小。

在对不同数据结构进行分析前,我们需要从最基础的问题开始:Java中的一个对象是以何种结构存储在内存里的?

2.2.1 Java对象内存结构模型

一个Java对象在内存中的存储结构通常包括对象头、实例数据与对齐填充。

对象头

对象头用于存储对象的标记位(Mark Word)与类型指针(Class Pointer)。

标记位里存储了包含锁状态与GC标记位等信息,其在32位系统上占存4字节而在64位系统上占存8字节。

类型指针是一个对象指向它元数据的指针,因此,其在32位系统上占存4字节,在64位系统上占存8字节。同时,若在64位机器上开启指针压缩参数-XX:UseCompressedOops,则此时类型指针在对象头中仅占存4字节。

另外,若实体为数组,则会额外有4个字节用以存储该数组的长度。

8c6a1bb61fcd80317e547bf8eec6cf91.png

实例数据

实例数据内部存储了对象所定义的所有成员变量。这些成员变量会紧密排列,若对象是由子类创建的,则其父类的成员变量也会包含在其中。若成员变量为NULL值,则不会给该成员变量申请指针空间。

对齐填充

若对象所申请的内存空间不是8的倍数,则JVM会添加合适的对齐填充使得整个对象所申请的空间为8的倍数。

综上,若一个简单实例对象内部存储一个int字段以及一个byte字段,那么在开启指针压缩的64位机器上其占存应为24字节。其中包含对象头的8字节标识位与4字节类型指针、内部字段int的4字节与byte的1字节以及对齐填充7字节。

1e0e317b28f14523bcc8a986be99f727.png

2.2.2 原生HashMap结构内存开销

基于上述的Java实例内存结构的基础理论,我们将以HashMap<Integer,Integer>为主要示例,详细探讨JDK原生HashMap内部结构以及其内存开销。

下表是一个简单的实验结果。我们统计了在开启指针压缩的64位机器上,不同数据条数的键值类型均为Integer的HashMap的内存占存。作为参考,我们将其所存储的所有Integer实例的占存视作数据占存,将剩余的占存视作结构占存。从下表的实验结果来看,无论在何种数据规模下,HashMap内部结构的内存开销占比都很高,占到了整体的55%以上。

数据量

HashMap占存

数据占存

结构占存

结构开销占比

32

2352

1024

1328

56.46%

512

36912

16384

20528

55.61%

4096

294960

131072

163888

55.56%

65536

4718640

2097152

2621488

55.56%

1048576

75497520

33554432

41943088

55.56%

需要知道此类现象的成因还是需要从HashMap内部存储结构为入手点进行分析。如下图所示,HashMap主要由一个哈希桶数组及多个存储在哈希桶中的节点Node<Key,Value>所构成。

d6f1e8d07b174f9d40c12e97745e6c69.png

下面我们来分别具体解析一下哈希桶数组table和数据节点Node的内存开销。

哈希桶数组table

哈希桶数组实际是用于存储数据节点Node的数组。程序将根据数据Key的HashCode运算得到数据节点Node实际应存储在哈希桶数组的哪一个下标位置。通过哈希桶打散数据后,程序可以通过Key快速的查找到实际数据节点。其在源码中实际定义如下:

9e6e9694933fe7f217582112522a3183.png

那么,在内存结构上,哈希桶就是由一个附带数组长度的对象头和数组元素集合组成。因此,一个长度为N的哈希桶数组的占存大小就会是:

8(对象头标识位)+ 4(类型指针)+ 4(数组长度 + 4 (实体引用)*N (实体数量)字节 + 对齐字节。即,长度为32的哈希桶数组则实际占存即为16 + 4 *32 = 144字节。

550cef0728c8717a7ccd71424d52fdf8.png

为了提升读写性能,HashMap中哈希桶数组的实际长度并不会总是等于实际存储的数据量。哈希桶数组的实际长度在两个时候会产生变化:

1)初始化

在HashMap进行实例创建的时候,程序会按照所指定的容量(默认为16)向上取最接近的2的整数幂作为实际初始化容量。该容量也是哈希桶数组的长度。比如外部创建一个指定容量为100的HashMap,则其内部哈希桶数组的实际初始长度为128。

2)扩容

HashMap为了确保其读写效率,当内部数据量到达一定规模时,会进行扩容操作。而其负载因子和当前哈希桶数组的长度二者相乘所得出的扩容阈值决定了扩容前在哈希表内部最大元素数量。例如,一个容量为32、负载因子为默认的0.75的HashMap的扩容阈值即为32*0.75=24。那么,当此HashMap被插入24条数据后,其内部的原先32长的哈希桶数组就会被扩容至原长度的2倍64。

综合上述的哈希桶长度策略,其实可以很明显的看到HashMap所存储的哈希桶实体数组在绝大部分情况下总是会将冗余出比实际数据量多一些的空间,以减少哈希碰撞、提升读取效率。

下表是在不同数据规模下哈希桶数组相对于普通实体数组,冗余的数组长度及其额外的开销。

数据量

实体数组长度

哈希桶数组长度

哈希桶数组冗余长度

冗余内存开销(字节)

50

50

128

78

312

200

200

512

312

1248

1000

1000

2048

1048

4192

10000

10000

16384

6384

25536

100000

100000

262144

162144

648576

那么依据上面的理论,我们就可以推算出不同数据量的HashMap中哈希桶数组的内存开销及其占比。

数据量

数据占存

哈希桶数组占存

总大小

哈希桶耗存占比

32

1024

272

2352

11.56%

512

16384

4112

36912

11.14%

4096

131072

32784

294960

11.11%

65536

2097152

262160

4718640

5.56%

1048576

33554432

4194320

75497520

5.56%

数据节点Node

Node类继承于 Map.Entry,是HashMap中存储数据的基本单元。其内部除了存储了键值对数据外,同时存储了节点的哈希值以及是当其在链表或红黑树中时,其下个Node节点的引用。

8e114f5e7c813eabf25601377180b283.png

那么,我们可以依据其内部结构如计算出一个Node实例的字节数为32个字节。若要使用Node存储32个Integer键值对,那么所有32个节点实体一共要占用1024个字节。

7cd14553631b5332282c624b4633f31f.png


那么我们可以在前面的实验数据中再添加上Node的数据,得到完整的HashMap内存开销各部分的占比:

数据量

数据占存

哈希桶数组占存

Node占存

总大小

32

1024

272

1024

2352

512

16384

4112

16384

36912

4096

131072

32784

131072

294960

65536

2097152

262160

2097152

4718640

1048576

33554432

4194320

33554432

75497520

所以,在原生HashMap消耗了大量的额外空间结构换取其读写性能。这使得原生HashMap在大数据量且内存有限的应用上,并不是一个最优的缓存结构选型。

2.2.3 包装类型损耗

由于Java的泛型机制,绝大部分的数据结构的存储的类型只能声明为包装类。因此,即使需要存储是整型等基础类型,也将其不得不转换为对应的包装类型来存储在内存中。这不仅会有存取时产生额外的装拆箱的性能损耗,存储包装类相较基础类型也会产生更大的内存开销。以实际应用场景中最为常见的整型为例,我们将简单比较一下Integer[] 和int[] 这两种数组的内存大小差异。

Integer[]

由于Integer是包装类,因此数组中存储的实际是4字节长的Integer的引用。而对于一个Integer实例本身来说,参考前文所述,除了4字节的实际数据外,其还需要12字节来保存其对象头。所以在集合中要保存一个Integer的实际开销就会是4 + 12 + 4 = 20字节。

3c5a89cc5404d6cc6ce4b92d81d63897.png

int[]

基础类型的int[]则简单的多:在创建数组时,仅需为每个元素开辟4字节来保存整型即可。

d664b94fb355d9d2762f46215a44802d.png

所以,理论上每个Integer都会比int额外产生16字节的内存开销 。从实验结果可以看出,若我们可以直接使用基础类型来代替包装类存储时,可以大幅减少内存占存。此结论对其他如HashMap等数据结构也同样有效。

数据量

int[]占存

Integer[]占存

额外开销占比

32

144

656

78.05%

512

2064

10256

79.88%

4096

16400

81936

79.98%

65536

262160

1310736

80.00%

1048576

4194320

20971536

80.00%

2.2.4 其他结构选型

为了在保证读写性能的情况下尽可能压缩内存开销,我们调研了一些第三方的开源集合框架来尝试在内存和性能上尽可能取得平衡。

ConcurrentHashMap

ConcurrentHashMap是HashMap的线程安全版本,内部也是使用数组、链表与红黑树的结构来存储元素。相较于同样JDK中线程安全的HashTable来说,其锁竞争更少、读写效率更高。

SparseArray

SparseArray即稀疏数组,是Android提供的建议替换HashMap<Integer, E>的用来存储整型类型对象键值对的类。其内部主要使用了数组作为存储方式,比HashMap<Integer, E>要高效轻量。

Guava Cache

Guava Cache是google开源的一款本地缓存工具库。其使用多个segments方式的细粒度锁,提供了支持高并发场景的线程安全的存储结构。

fastutil

FastUtil是一个高性能的集合框架,提供了以基础类型为元素的集合来代替JDK原生的集合类型。基础类型为元素的集合避免了大量的基础类型的装箱拆箱。因此,在程序进行集合的遍历、根据索引获取元素的值和设置元素的值的时候,fastutil可以提供更快的存取速度以及更低的内存消耗。

我们实验了整型键值对不同数据规模下各个集合的内存占比,并且用HashMap的数据作为基准进行横向比较。实验结果具体数据如下所示。

数据量

HashMap

ConcurrentHashMap

SparseArray

Guava

fastutil

32

2352

2368

832

4344

624

256

18480

18496

6208

27568

4208

1024

73776

73792

24640

106672

16496

32768

2359344

2359360

786496

3397104

524400

1048576

75497520

75497536

25165888

108970384

16777328

9f3ac948585bf545007808500803dc35.png

3bf11e6837802308a9c295fe8865446d.png

三、数据编码压缩

在实际应用场景下,几乎所有需要缓存的数据都有着比较高的重复率或是其他分布规律。在内存结构选型的基础上,针对于不同的数据特征,我们可以采用不同的数据编码压缩方式对数据进行压缩处理,进一步降低缓存的内存开销。

下面,我们将介绍几种常用有效的数据编码压缩方式。

3.1 常用编码技术

3.1.1 位图编码

位图(BitMap)是一种常见的编码格式,JDK中提供的默认实现为BitSet类。它是用Bit位来存储数据的某种状态,通常指示是非有无。在最常见的情况下,当需要存储大量连续ID是否为True时,用到此类结构就可以大量减少内存的开销。

在下例中,需要存储的数据的Key为整型, Value为该Key是否有效的状态数据。若是直接存储,则一条数据至少需要4个字节用于存储整型Key以及4个字节用于存储布尔型的状态值。那么,当需要存储Key从1至8的8条数据,则至少需要64字节。若使用位图编码技术对数据进行处理,那么我们仅需要1个bit即可存储一个True or False的状态信息。因此,就可以使用1个字节存储下所有8条数据的状态信息。此时,该字节的第1位的bit用以表示Key = 1的状态信息,第2位的bit用以Key = 2的状态信息,以此类推。

910042c89b1a8448f5fe9caef51a4cb7.png

下表是在64位机器上开启指针压缩后, Java原生HashMap与BitSet的耗存对照表。可以明显地看出,整体压缩效率是非常高的。在实际业务场景下,只要整型Key分布相对密集,就可以利用位图编码达到不错的压缩效果。

最大ID

HashMap占存

BitSet占存

1w

532.84KB

2.04KB

10w

5.57MB

16.04KB

100w

53.77MB

128.04KB

1000w

521.76MB

2.00MB

另外,位图编码还可以额外扩展至一些类型较少的枚举类型上。例如,枚举类Season只有4种元素,则可以使用2个bit来代表一个属性,那么则只需8bit即可存储id从1-4的 4个Season枚举。

enumSeason{
       Spring,Summer,Fall,Winter;
}

ff442ff6216017536a8afc141dd4567a.png

3.1.2 游程编码

游程编码(Run-length encoding,RLE)是一种无损压缩数据的编码方式,主要方法是使用当前数据元素以及该元素连续出现的次数来取代数据中连续出现的部分。若数据存在大量的数据连续且重复的情况,就可以考虑使用RLE以降低内存。

比如,一个内部存储了4个连续的a与6个连续的b的字符串经过游程编码后为a4b6。那么,该字符串长度就从10字节减少至4字节。

084c4a8b8fb55056bb6ff322b268948c.png

3.1.3 字典编码

字典编码是把整体重复性高的数据进行去重后建立字典,把原来存放数据的地方变为指向实体字典引用的编码方式。因为引用指针依然占存,因此适合单个的实例数据字段较多的数据缓存。

下例为原始数据为整型Key查询长字符串Value的场景。首先,将重复的字符串实体数据提取出来,将其单独作为一个实体字典进行存储。该字典Key为一个指针,Value则为提取出的不重复的字符串数据。然后,原始字典的Value就可以变为一个指针,指向新实体字典的Key。当需要查询Key1内实际数据的时候,先在原始字典中查询到引用Ref1,再在实体字典查询Ref1即可获得其Value值aaaa。

bbddb5a459897c6590f507c0e173f47b.png

3.1.4 差值编码

差值编码是对于非连续的数据Key通过差值计算的方式转化为连续的Key,让字典可以转化为数组的编码方式。

下例中的数据Key为日期,Value为一个整型。在日期相对连续的情况下,取所有日期的最小值为开始日期,以数据生效日期到开始日期的差值为新字典的Key。那么编码前旧数据字典的Key为Date类型,而编码后的新数据字典的类型则可以转化为更小更泛用的int型。

daec6f17f3356ebd864a4299dd82addb.png

下表是在N天连续的日期查整型的场景下,原生HashMap与编码后整型数组的耗存对照表。即使连续的日期数量较小,也可以看出整体存在的巨大差距。在实际的应用场景下,此类编码方式一般用于连续日期的缓存,也可以扩展到其他有着类似数据特征的缓存上。

日期数量

HashMap占存

编码后整型数组占存

100

8.09KB

440B

1w

767.19KB

39.10KB

1000w

750.65MB

38.15MB

以上的编码技术可以在不同的数据场景下进行组合使用,接下来我们将简要的展示两个实际酒店查询服务缓存内存压缩优化的案例。

3.2 应用案例

3.2.1 房型基础信息

查询服务缓存了上亿条房型信息数据。在请求处理过程中,服务可以在缓存中通过房型ID查询到该房型的信息。因为数据条数上亿且实体内部字段很多,因此未优化的缓存在内存中占存高达上百GB,是一个较大的内存性能瓶颈。

因此,针对该缓存,我们使用了位图编码以及字典编码,大幅降低了其内存开销。

1)使用位图编码对可枚举字段进行数据压缩

我们将房型数据实体上包括布尔型、枚举以及部分字符串等所有可以枚举的字段进行了位图编码,大幅降低了单个实体的占存大小。比如在下方作为例子的字段中,RoomType虽然存储为一个String,但是在实际业务场景中它一共只有5种取值可能性,因此也可以作为枚举类进行处理为3个bit。

f65a97593e8ce4ab4066f5e06eb0485a.png

在原先存储方式的情况下,示例的一个房型实体字段就至少需要16字节,通过位图编码后一个房型实体字段实际仅需要10个bit即可无损的存储下所有有效信息。

cec93e085ff98a32038449d779c97c6a.png

2)使用字典编码对重复实体进行压缩

经过线上数据统计与分析,我们发现部分房型属性数据的重复率达到99%。也就是说,虽然存在上亿个房型,但是实际去重后的房型的部分基础信息实体只有百万级。因此,在对房型基础信息实体本身进行位图编码的同时,我们采用了字典编码的方式对房型ID不同但内部字段信息完全重复的数据实体进行字典编码,以压缩这部分的消耗。

在实际处理过程中,我们会先将房型数据实体进行序列化后转换为MD5,在房型字典中只存储MD5编码,而实体字典中存储MD5到实际房型信息实体的关系。在进行数据查询时,则是先通过房型ID在房型字典中查找到对应的MD5值,然后在实体字典中通过MD5值查找到对应的房型基础信息实体。

7f7d24d8231279c8bf12485abb5bc2c4.png

经过上述两个编码压缩优化后,房型实体缓存占存整体压缩率达到2%以下,节省了数十GB的内存空间。

3.2.2 单天房价信息

单天房价信息缓存是存储每个房型每日价格的缓存,是查询服务数据量最大同时也是最核心的数据缓存。在应用请求处理过程中,会使用房型ID以及日期从该缓存中获取房型某一天的价格数据。

下面将以单房型下存储的日期信息为例,逐步展示数据压缩优化的全部过程。

1)使用字典编码对每日重复的价格信息进行编码

首先,将所有该房型上出现的价格提取并存储到一个价格数组上,在数据字典里则存储实际价格数据在价格字典的索引。同时,因为存在可能没有数据的日期,因此Null值被存储在所有价格数组上的第一个偏移index上,作为默认值。

f300f7de9d63bb961f98caf8ad44fe2e.png

2)使用差值编码处理日期

因为在绝大部分情况下,数据字典中的日期均为连续的,且从业务场景上来说最大的日期也不会过大,因此我们采用差值编码处理日期,将数据字典中的日期替换为与服务器启动日期之间相差天数的偏移量。此时,数据字典的Key则会变为一个从0开始的int,那么就可以使用占存更小的数组来表示这个数据字典。该数据索引数组为一个int[],其下标表示日期偏移,值表示到价格字典的索引。

d72283b515c7a4df673e9f69cb5782cc.png

3)使用位图编码处理可枚举的价格索引

因为单个房型下的价格数量是有限的,因此同样可以视作是枚举值的一种。对枚举值,就可以使用位图编码对数据索引数组进行压缩。在下图的数据样例中,因为价格数组长度为3,即存在3种可能的价格,因此将int转换为2个bit进行存储,则位图的一天的偏移步长为2。

7286f5e24d59c16364efdc4b5840646b.png

4)使用RLE编码处理末尾

在很多房型下的到天价格数据,在距离现在最远的日期带的价格通常都是重复的,因此,我们可以使用RLE的方式对末尾重复的数据进行截尾,来进一步压缩数据位图大小。

a7a27c17d009ae121abaf45f5a49eca1.png

在所举的例子中,其在内存中单对象实例数据部分的内存可以从最初的数百字节降低至最终的31字节。而在实际业务场景中,该单天房价数据经过压缩处理后实际压缩率为60%左右。

9156cfe57229ef422027a0a038065d18.png

四、总结

本文主要介绍了携程酒店查询服务在本地缓存数据结构选型以及优化方面的探索与实际应用案例。

在常规缓存数据的存储结构选型上,我们先根据缓存场景的需求,分析比较了不同数据结构后,选择线程安全的Map结构作为基础研究方向。然后基于Java对象占存的原理,以原生HashMap为实际案例,详细分析了其内存实际占用的分布,并比对了多种常见的用于缓存的内存结构的优劣势。

6ccf403400b10f6bfcecf0acbeb3b227.png

在进一步优化的时候,针对不同类型的数据可以进行选择不同的编码方式,并以两个实际的缓存压缩方案为例,介绍了如何组合的使用此类编码来有效压缩本地缓存的内存大小。

874b2b501093bebf130b690e93a2828f.png

综上所述,虽然存储在服务器内存中的缓存成本较高,但是若合理的进行数据选型并采用合适的优化方法,则可以达到使用少量的内存缓存大规模的数据,以较低的成本大幅提高应用的响应性能的目的。

参考资料

Java对象布局:

https://www.jianshu.com/p/91e398d5d17c

fastutil官网:

https://fastutil.di.unimi.it/

Guava github:

https://github.com/google/guava

SparseArray文档: 

https://developer.android.com/reference/android/util/SparseArray

【推荐阅读】

76ed5307e9dc727f5d0b02492bbba18a.png

 “携程技术”公众号

  分享,交流,成长

IL-17C靶点全解析:中和抗体SIM0543的实验设计与应用指南 炎症和自身免疫疾病研究离不开对抗体工具的深入理解。IL-17家族在炎症信号传导中扮演关键角色,其中IL-17C作为一种主要由上皮细胞表达的细胞因子,通过IL-17RA/IL-17RE受体复合物激活NF-κB和MAPK通路,形成正反馈环路,参与银屑病、炎症性肠病等疾病的病理过程。中和抗体通过特异性阻断配体与受体结合,为解析靶点功能和验证病理机制提供了重要手段。在体外细胞实验和动物疾病模型中,合理设计浓度梯度、设置同型对照、检测下游炎症因子,是获得可靠数据的基础。围绕重组抗人IL-17C单克隆抗体SIM0543 阅读详情

相关推荐

基于MPC17C724与HCS08 MCU的紧凑型步进电机驱动方案详解

在嵌入式系统与自动化控制领域,电机驱动是连接数字指令与物理运动的核心环节。其基本原理是通过微控制器(MCU)产生控制信号,经由功率驱动电路(如H桥)来精确调节电机的转向、速度与位置。这种技术方案的价值在于能够以极低的成本和紧凑的尺寸,实现对执行机构的精准闭环或开环控制,广泛应用于消费电子、小型自动化设备及精密仪器中。其中,飞思卡尔(现NXP)的MPC17C724双H桥驱动器与HCS08系列8位MCU的组合,便是一个经典的工程实践案例。该方案充分利用了MPC17C724高集成度、内置防直通保护等特性,与HCS

weixin_30664615的博客 1912

【codeforces 17C】Balance

题目:Balance 题意:        计数类DP。        给出一个长度为n的只由a,b,c组成的字符串,每次操作可以把一个字母变成其相邻的某个字母,定义一个字符串是平衡的当且仅当三个字母出现的次数相差不超过1,问这个字符串经过若干次操作可以变成多少个不同的平衡的字符串。 解析:        参见CodeForces 17 C.Balance(dp)        重点在...

ETO的博客 38万+

从NASA DataPort下载到PyTorch Dataset:手把手处理USGS光谱库的AVIRIS数据

本文详细介绍了如何从NASA DataPort下载USGS光谱库的AVIRIS高光谱数据,并将其转化为PyTorch Dataset对象。内容涵盖数据获取、预处理、辐射定标及高效数据加载技巧,特别针对AVIRIS数据的水吸收波段剔除和波段分组标准化进行了重点讲解,为环境监测、地质勘探等领域的研究者提供实用指南。

weixin_30607659的博客 281

17C 颜色与物质浓度辨识做题笔记

关键词:多元线性回归、非线性、层次分析、相关性分析、变异系数、异常值处理、优劣评价。总结:第一二问相关性+多元回归 第三问 看变化维度数和样本数进行效果检验。使用权重标准差和 除以 数据量 定义为数据优劣度(每个样本各特征的平均偏移)数据优劣评价:间距、离散程度、样本量等,最好能用指标进行综合评价(论文3)第二题:建立颜色读数和物质浓度的数学模型,并给出模型的误差分析。第一题:建立颜色-浓度模型,对五组数据优劣进行评估,给出准则。第三题:探讨数据量和颜色维度对模型的影响。回归效果不好说明数据不好…

LZM_SZU的博客 78万+

ArcGISPro应用指南:ArcGISPro制图全流程详解

本文介绍了基于ArcGIS Pro的地图制作基本流程,旨在帮助读者快速掌握从数据加载到地图输出的完整制图操作。文章首先简要概述了GIS技术在当今数据驱动时代的重要作用,并引出ArcGIS Pro作为专业GIS平台的优势与应用价值。随后,内容围绕地图制作的核心步骤展开,包括数据加载、布局创建、地图范围设置以及各类地图要素(如指北针、比例尺、图例、图名)的插入与样式调整。此外,还介绍了如何通过添加图形、文本及动态元素增强地图表达效果,并最终完成地图导出设置与文件输出格式的选择。

weixin_45812624的博客 1356

硬件工程师必看:5种ESD保护电路设计实战解析(附电路图)

本文为硬件工程师深度解析5种实战ESD保护电路设计,涵盖TVS二极管、专用保护芯片、片上防护及高速射频场景方案。通过具体电路图,详解工作原理、选型要点及PCB布局黄金法则,重点强调ESD防护设计中的能量泄放路径管理与低阻抗实现,助力提升产品可靠性。

xk7n92h3p5m的博客 637

codeforces 17C Balance(动态规划)

codeforces 17C Balance 题意 给定一个串,字符集{'a', 'b', 'c'},操作是:选定相邻的两个字符,把其中一个变成另一个。可以做0次或者多次,问最后可以生成多少种,使得任意一种字符和其他字符的个数相差都不超过1. 题解 一个生成串压缩之后必定都是初始串的子序列,那么只要能枚举所有子序列,其他的很好搞定。 对于枚举的每个子序列,如果它是由初始串最早能生成的产生,那么肯定...

weixin_30426879的博客 14万+

SPICE模型入门到精通

文章目录前言一、SPICE是什么?二、1. 前言 由于笔者的毕业设计与功率半导体器件建模相关,需要用到SPICE软件来建模,在这记录一下从零开始的SPICE学习历程~ 一、SPICE是什么? 电子产品的设计一般先从功能框图开始,然后细化到原理图,还要经过很调试验证过程,最终才能完成。调试和验证的过程复杂而繁琐,在设计集成电路时更是如此,在制造之前急需验证集成电路的功能。 根据电路理论,人们可以建立起节点方程和回路方程,通过解这些方程组成的方程组就可以得到结果,也就是说可以通过计算来获得电路的工作情况。但

qq_42031914的博客 7万+

CF_17C Balance

cf 17c C. Balance http://codeforces.com/problemset/problem/17/C 题意: 给出一个最多150字符长的只有a、b、c组成的字符串,每次操作可以选择两个连续的字符: 1.把前面一个字符变成后面一个字符 。 2.把后面一个字符变成前面一个字符 。

JFGiGi的博客 11万+

关于RS485通讯TVS器件选择的经验

先说经验结论 如果你的RS485用于频繁热拔插, 比如作为手持终端使用, 且手持器与目标板非隔离, 那么使用6.8CA可能是更好的选择.因为有热拔插会产生浪涌, 而且在非隔离的场合有些工业设备接地也是无法保障的.对于使用场景较为单一的, 点对点或点对多场合. 可以使用最大峰值脉冲电流较小的SM712.这是在长期的产品设计中得到的经验. 仅供参考 一. TVS的工作原理和作用 瞬态电压抑制二极管 ...

qq_20163075的博客 6509

3D Gaussian Splatting:用于实时的辐射场渲染

3D Gaussian Splatting:用于实时的辐射场渲染 3d gaussian splatting for real-time radiance field rendering

m0_51976564的博客 1万+

CodeForces 17C Balance (DP)

题目类型  DP 题目意思 给你一个最多100字符长的字符串, 问是否满足这个格式 -> @[/resource] 其中 是一个只包含 字母或数字或下划线 的长度为 1 - 16 的字符串      是一个由 若干个以字符 '.' 进行分隔的单词 组成的长度为 1 - 32的字符串, 其中单词的限制和 一样      resource的限制与 一样  [/resource] 的

yunyouxi0's blog 7万+

【Bluetooth蓝牙开发】一、开篇词 | 打造全网最详细的Bluetooth开发教程

1、开篇词 2、蓝牙开发入门 3、蓝牙协议栈总览 4、蓝牙协议栈——物理层 5、蓝牙协议栈——链路层 6、蓝牙协议栈——传输层 7、蓝牙协议栈——L2CAP协议 8、蓝牙协议栈——ATT协议 9、蓝牙协议栈——GATT协议 10、蓝牙通信流程分析 11、蓝牙协议栈——Bluez交叉编译 12、蓝牙调试工具汇总

Donge's Blog 1万+

Flask部署(从17C开始)

https://github.com/miguelgrinberg/flasky Flask部署过程一、 创建一个本地git仓库,把17C版本的文件(不包含.git这个隐藏文件)复制到仓库中并加以修改。 注意此时仓库中应该有requirements.txt文件,这是为了让heroku识别的需求文件; 注意此时仓库中应该有.gitignore文件,这是git上传时不仓库中不需要上传的文件表; 注意修

Zerore的博客 8万+

振动分析-46-设备振动监测和故障诊断能力提升需要理解和掌握的知识体系

提升振动分析能力对于确保机械设备的高效运行和预防故障至关重要。

qq_20466211的博客 664

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

CodeForces - 17C Balance【DP】

题目链接:https://codeforces.com/contest/17/problem/C #include <iostream> #include <cmath> using namespace std; static const int MOD=51123987; static const int MAXN=150+10; char s[MAXN]; int p[MAXN][3]; int dp[MAXN][55][55][55]; int n,res; int main(

inv0ker的博客 2万+

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

从1乘到17c语言,《中文笔记》第一册(17c)

卷三《跳月記》。此文以選入《虞初新志》,遂傳誦於世。《新志》錄雲士文,亦以此為最佳。後半〝是時也,有男近女而女去之者,有女近男而男去之者;有數女爭近一男,而男不知所擇者;有數男競近一女,而女不知所避者;有相近復相捨,相捨仍相盼者。目許心成,籠來笙往,忽焉挽結。於是妍者負妍者,媸者負媸者。媸與媸不為人負,不得已而後相負者。媸復見媸,終無所負,涕洟以歸,羞愧於得負者〞云云,洵足當高江村評所謂〝筆舞墨歌...

weixin_30611317的博客 1万+

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

codeforces 17c

题意:有一个只由abc构成的字符串,可以进行2种操作,把相邻的两个字母前一个换成后面的或把后面的换成前面的。比如ab操作一次可以变成aa或bb。一个字符串称作“平衡的”指它里面出现abc的个数两两只差不超过1。问经过任意次操作后得到的字符串有多少个是“平衡的”。 思路:对于字符串s,把s的相邻的相同的字符都缩成一个,比如“aaab”缩成“ab”。如果b缩完后的字符串是a缩完后的子序列,那么b能通过...

zekunzhao的博客 1万+

Gym - 100917C . Constant Ratio

C. Constant Ratio time limit per test 1.0 s memory limit per test 256 MB input standard input output standard output Given an integer n, find out number of ways to represent it as

米兰的小科比 3万+

【树莓派】树莓派4B运行gpio readall报Model:17c错误的解决方法

使用树莓派4B运行gpio readall Oops - unable to determine board type... model: 17 原因: 版本是V2.5版本导致,需要升级只2.52版本 升级方法: http://wiringpi.com/wiringpi-updated-to-2-52-for-the-raspberry-pi-4b/ To upgrade: cd /tmp wget https://project-downloads.drogon.net/wiringpi-latest.

Not Found 404 9526

编程马拉松17C

#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#inclu

lovewangtaotao的专栏 3万+

codeforces 17C C. Line(exgcd解不定方程)

题目链接:codeforces 17C题目大意:解Ax+By+C = 0题目分析:拓展欧几里得解不定方程模板题AC代码:#include <iostream> #include <cstdio> #include <algorithm> #include <cstring>using namespace std;typedef long long LL;LL exgcd ( LL a , LL b

llin-黎辰 1万+

CodeForces 17C 平衡字符串(Balance)

CF17C 扎心的题面,考试的时候题面是”相邻字符串”花了两个小时死活没想出,回头看了cf发现tm竟然是“相邻字符”!(雾) 如果cf的题面是正确的,那么这道题就可以轻易地用四维dp完成。定义初始字符串为A,操作后为B,将两者去重(删除相邻的同一种元素)形成子串a和b。手推可得b一定是a的子串(虽然不一定是真子串)。 如上我们可以构造一个长度为n的字符串,要求a、b、c字符个数之差均不超过1...

xyc1719的博客 1万+

CodeForces - 17C Balance(DP)

Description Nick likes strings very much, he likes to rotate them, sort them, rearrange characters within a string... Once he wrote a random string of characters a, b, c on a piece of paper and b

WZJRJ28 1万+

【Codeforces自我陶醉水题篇~】(差17C code....)

Codeforces17A 题意: 有一种素数会等于两个相邻的素数相加 如果在2~n的范围内有至少k个这样的素数,就YES,否则就NO; 思路: 采用直接打表,后面判断一下就好了。那个预处理素数表还是满赞的~~ 不多说,上code……………. #include<bits/stdc++.h> #include<string.h> usin...

weixin_30896825的博客 3720

BioSIM抗人IL-17C抗体SIM0543:赋能炎症与自身免疫性疾病研究

BioSIM anti-Human IL-17C Antibody (MOR106 Biosimilar) 以其卓越的品质、广泛的适用性和便捷的服务,正成为越来越多科研工作者的首选。这一过程不仅提升了抗体的稳定性和活性,也减少了非特异性结合的风险,从而提高实验结果的可靠性和可重复性。如果您正在寻找一款高性能、高稳定性的 IL-17C 抗体产品,不妨考虑 BioSIM anti-Human IL-17C Antibody (MOR106 Biosimilar),它将是您科研旅程中的得力助手。

2501_92529723的博客 2147
上一篇: 干货 | 携程基于BookKeeper的延迟消息架构落地实践
下一篇: 干货 | 携程公共技术支持运营实践
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值