Redis核心技术与实战【学习笔记】 - 10.浅谈CPU架构对Redis性能的影响

概述

可能很多人都认为 Redis 和 CPU 的关系简单,Redis 的线程在 CPU 上运行,CPU 快 Reids 处理请求的速度也很快。

其实,这种认知是片面的,CPU 的多核架构多 CPU 结构,也会影响到 Redis 的性能。如果不了解 CPU 对 Redis 的影响,那么在进行 Redis 调优时,可能会遗漏一些调优方法,不能把 Redis 的性能发挥到极致。


1. 主流的 CPU 架构

一个 CPU 处理器中,一般有多个运行核心,我们把一个运行核心称为一个物理核,每个物理核都可以运行应用程序。每个物理核都拥有私有的一级缓存(Level 1 cache,简称 L1 cache),包括一级指令缓存和一级数据缓存,以及私有的二级缓存。
在这里插入图片描述

这里提到了一个概念,就是物理核的私有缓存。它其实是指缓存空间只能被当前这个物理核试验,其他的物理核无法对这个核的缓存空间进行数据存取。

因为 L1 和 L2 缓存是每个物理核私有的,所以,当数据或指令保存在 L1、L2 缓存时,物理核访问它们的速度非常块,不超过 10 纳秒。如果 Redis 把要运行的指令或存取的数据保存在 L1 和 L2 缓存的话,就能高速地访问这些指令和数据。

不过,由于技术限制,L1 和 L2 缓存一般只有 KB 级别,存不下太多的数据。如果 L1、L2 缓存中,没有所需的数据,应用程序就要访问内存来获取数据。而应用程序的访问内存延迟一般在百纳秒级别,是访问 L1、L2 缓存的延迟的近 10 倍,不可避免地会对性能造成影响。

所以,不同的物理核还会共享一个共同的三级缓存(Level 3 cache,简称 L3 cache)。L3 缓存能够使用的存储资源比较多,一般比较大,能达到几 MB 到几十 MB ,这就能让应用程序缓存更多的数据。当 L1、L2 缓存中没有数据缓存时,可以访问 L3,尽可能避免访问内存。

另外,目前主流的 CPU 处理器中,每个物理核通常都会运行两个超线程,也叫作逻辑核。同一个物理核的逻辑核会共享使用 L1、L2 缓存

在这里插入图片描述
在主流的服务器上,一个 CPU 处理器,会有 10 到 20 多个物理核。同时,为了提升服务器的处理能力,一个服务器上通常还会有多个 CPU 处理器(也称为多 CPU Socket),每个处理器有自己的物理核(包括 L1、L2 缓存),L3 缓存,以及连接的内存,同时,不同的处理器间通过总线连接。

在这里插入图片描述
在多 CPU 架构上,应用程序可以运行在不同的处理器上。在上图中,Redis 可以先在 Cpu Socket1 上运行一段时间,然后再被调度到 Cpu Socket2 上运行。

这里有个场景:如果应用程序现在一个 Socket 上运行,并且把数据保存到了内存,然后被调度到另一个 Socket 运行,此时,应用程序再进行内存访问时,就需要访问之前 Socket 上连接的内存,这种访问属于远端内存访问和访问 Socket 直接连接的内存相比,远端内存访问会增加应用程序的延迟

在多 CPU 架构下,一个应用程序访问所在 Socket 本地内存和访问远端内存的延迟并不一致,所以,我们也把这个架构称为非统一内存访问架构(Non-Uniform Memory Access,NUMA 架构)。

现在,我们知道了主流的 CPU 多核架构和多 CPU 架构,总结下 CPU 架构对应用程序运行影响:

  • L1、L2 缓存中的指令和数据访问速度很快,所以,充分利用 L1、L2 缓存,可以有效缩短应用程序的执行时间。
  • 在 NUMA 架构下,如果应用程序从一个 Socket 上调度到另一个 Socket 上,就可能出现远端内存访问的情况,这会直接增加应用程序的执行时间。

接下来,我们了解下 CPU 多核是如何影响 Redis 性能的。

2. CPU 多核对 Redis 性能的影响

在一个CPU 核删高云翔时,应用程序需要记录自身使用的软硬件资源信息(如栈指针、CPU 核的寄存器值等),我们把这些信息称为运行时信息。同时,应用程序访问最频繁的指令和数据还会被缓存到 L1、L2 缓存上,以便提升执行速度。

但是在多核 CPU 场景下,一旦应用程序需要在一个新的 CPU 核上运行,那么,运行时信息就需要重新加载到新的 CPU 核上。而且,新的 CPU 核的 L1、L2 缓存也需要重新加载指令和数据,这会导致程序运行时间增加。

有个曾经在多核 CPU 场景下调优的案例,可以帮助我们理解到多核 CPU 对 Redis 性能的影响。

需求是要对 Redis 99% 的尾延迟进行优化,要求 GET 尾延迟小于 300 微秒,PUT 尾延迟小于 500 微妙。

尾延迟解释
我们把所有请求的处理延迟从小到大排序,99% 的请求延迟小于的值就是 99% 尾延迟。 比如说我们有 1000 个请求,假设按从小到大排序后,第 991 个请求的延迟实测值是 1ms,而前 990 个请求的延迟都小于 1ms,所以,这里的 99% 尾延迟就是 1ms。

我们使用 String类型(复杂度为 O(1)) 的 GET/PUT 进行数据存取,同时关闭 AOF 和 RDB,而且 Redis 中没有保存集合类型的数据,也就没有 bigkey 操作,避免了可能导致延迟增加的许多情况。

但是,即使这样,我们在一台有 24 个 CPU 核的服务器上运行 Redis 实例,GET 和 PUT 的 99% 的尾延迟分别是 504 微妙 和 1175 微妙,明显大于设定的目标。后来,我们仔细检测了 Redis 实例运行时的服务器 CPU 的状态值,这才发现, CPU 的 context switch 次数比较多。

context switch 是指上下文切换,这里的上下文就是线程的运行时信息。在 CPU 多核环境中,一个线程先在一个 CPU 核上运行,之后又切换到另一个 CPU 核上运行,这是就会发生 context switch

context switch 发送后,Redis 主线程的运行时信息需要被重新加载到另一个 CPU 核上,而且,此时,另一个 CPU 核上的 L1、L2 缓存中,并没有 Redis 实例之前运行时频繁访问的指令和数据,所以,这些指令和数据都需要从 L3 缓存,甚至是内存中加载。这个加载过程需要花费一定时间。而且,Redis 实例需要等待这个重新记载的过程完成后,才能开始处理请求,所以,这也会导致一些请求的处理时间增加。

如果在 CPU 多核场景下,Redis 实例被频繁地调度到不同的 CPU 核上运行的话,那么,对 Reids 实例的情趣处理时间影响就更大了。每调度一次,一些请求就会收到运行时信息、指令和数据重新过程的影响,这就会导致某些请求的延迟明显高于其他请求。分析到这里,我们就知道了刚刚的例子中 99% 的尾延迟时钟降不下来的原因了。

所以,我们要避免 Redis 总是在不同 CPU 核上来回调度执行。我们可以使用 taskset 命令把一个程序绑定在一个核上运行。

比如,下面的命令就把 Redis 绑定在了 0 号 核上,其中,“-c” 选项用于设置要绑定的核编号。

taskset -c 0 ./redis-server

我自己本地进行了测试, 使用的命令如下所示
taskset -c 0 /home/chenjian/redis-6.2.12/src/redis-server /home/chenjian/redis-6.2.12/redis.conf
此外,若要查询 Redis 进程与哪些核绑定,可以使用 taskset -pc pid 命令查看

  1. 首先使用 ps -ef|grep redis 查看 redis 的 pid,本机查到的 pid 为 93
  2. taskset -pc 93 查看绑定的 CPU 核
    在使用 taskset 命令绑定之前,Redis 运行时绑定 CPU 核为 0-7,也就是与电脑的所有的 CPU 核绑定
    在这里插入图片描述
    而在使用了 taskset -c 0 ./redis-server 绑定了 0 核后,通过命令可以看到,的确是绑定到了一个核上了。
    在这里插入图片描述

绑定后,进行了测试,发现 Redis 实例的 GET 和 PUT 的 99% 尾延迟一下子就分别讲到了 260 微妙和 482 微妙,达到了预期的目标。

在这里插入图片描述

可以看到,在 CPU 多核的环境下,通过绑定 Redis 实例和 CPU 核,可以有效降低 Redis 尾延迟。当然,绑核不仅对降低尾延迟有好处,同样也能降低平均延迟、提升吞吐率,进而提升 Redis 性能。

3.CPU 的 NUMA 架构对 Redis 性能的影响

在实际应用 Redis 时,经常会有一种做法,为了提升 Redis 的网络性能,把操作系统网络中断处理程序和 CPU 核绑定。这个做法可以避免网络中断处理程序在不同核上来回调度执行,有效提升 Redis 的网络处理性能。

但是,网络中断处理程序是要和 Redis 实例进行网络数据交互的,一旦把网络中断处理程序绑核后,我们就需要注意 Redis 实例是绑在哪个核了,这会关系到 Redis 访问网络数据的效率高低。

先看下 Redis 实例和网络中断处理程序的数据交互:

  1. 网络中断处理程序从网卡中读取数据,并把数据写入到内核的缓冲区
  2. 内核会通过 epoll 机制触发事件,通知 Redis 实例
  3. Redis 实例再把数据从内核缓冲区拷贝到自己的内存缓冲区

在这里插入图片描述
那么,在 CPU 的 NUMA 架构下,当网络终端处理程序、Redis 实例分别和 CPU 核绑定后,就会有一个潜在的风险: 如果网络中断处理程序和 Redis 实例各种所绑定的 CPU 核不在同一个 CPU Socket 上,那么 Redis 实例读取网络数据时,就需要跨 CPU Socket 访问内存,这个过程会花费较多时间

在这里插入图片描述
上图中,可以看到,网络中断处理程序绑定在了 CPU Socket 1 的某个核上,而 Redis 实例则被绑定在了 CPU Socket 2 的某个核上。此时,网络中断处理程序读取到的网络数据,被保存在 CPU Socket 1 的本地内存中,而 Redis 要访问网络数据时,就需要 CPU Socket 2 通过总线把内存访问命令发送到 CPU Socket 1 上,进行远程访问,时间开销比较大。

为了避免 Redis 跨 CPU Socket 访问网络数据,我们最好把网络中断处理程序和 Redis 实例绑在同一个 CPU Socket 上,这样一来,Redis 实例就可以直接从本地内存读取网络数据了。

在这里插入图片描述
不过,需要注意的是,在 CPU 的 NUMA 架构下,对 CPU 核的编号规则,并不是先把一个 CPU Socket 中的所有逻辑核编完,再对下一个 CPU Socket 中的逻辑核编码,而是先给每个 CPU Socket 中的每个物理核的第一个逻辑核依次编号,在给每个 CPU Socket 中的物理核的第二个逻辑核依次编号

举个例子:假设有 2 个 CPU Socket ,每个 CPU Socket 上有 6 个物理核,每个物理核又有 2 个逻辑核,总共 24 个逻辑核。我们可以执行 lscpu 命令,查看到这些核的编号:

lscpuArchitecture: x86_64 ... 
NUMA node0 CPU(s): 0-5,12-17 
NUMA node1 CPU(s): 6-11,18-23 
...
  • NUMA node0 的 CPU 核编号是 0-5、12-17。
    • 0-5 是 node0 上的 6 个物理核中的第一个逻辑核的编号。
    • 12-17 是 node上的 6 个物理核中的第二个逻辑核的编号。
  • NUMA node1 的 CPU 核编号规则和 node0 一样。

所以在绑核时,我们一定要注意,不能想当然的任务第一个 Socket 上的 12 个逻辑核的编号就是 0-11。否则,网络中断处理程序和 Redis 实例分别绑定到编号为 1 和 7 的 CPU 核上,此时,它们仍然是在 2 个 CPU Socket 上,Redis 实例仍然需要跨 Socket 读取网络数据。

所以,你一定要注意 NUMA 架构下 CPU 核的编号方法,这样才不会绑错核

简单总结下刚刚的内容。在CPU 多核场景下,用 taskset 命令把 Redis 实例和一个核绑定,可以减少 Redis 实例在不同的核上来回调度执行的开销,避免较高的尾延迟;在多 CPU 的 NUMA 架构下,如果你对网络中断处理程序做了绑核操作,建议你同时把 Redis 实例和网络中断处理程序绑在同一个 CPU Socket 的不同核上,这样可以避免 Redis 跨 Socket 访问内存中的网络数据的时间开销。

4.绑核的风险和解决方案

当我们把 Redis 实例绑到一个 CPU 逻辑核上时,就会导致子进程、后台线程和 Redis 主线程竞争 CPU 资源,一旦子进程或后台线程占用 CPU 时,主线程就会被阻塞,导致 Redis 请求延迟增加。

方案一:一个 Redis 实例对应绑一个物理核

在给 Redis 绑核时,我们不要把一个实例和一个逻辑核绑定,而要和一个物理核绑定,也就是说,把一个物理核的 2 个逻辑都用上。

还是以刚才的 NUMA 架构为例, NUMA node0 的CPU 核编号是 0-5、12-17。其中 1 和额 12、1 和 13、2 和 14 都表示一个物理核的 2 个逻辑核。所以,在绑核时,我们使用属于同一个物理核的 2 个逻辑核进行绑定操作。例如,我们执行下面的命令,就把 Redis 实例绑定到了 逻辑核 0 和 12 上,而这两个核正好都属于物理核 1。

taskset -c 0,12 ./redis-server

和只绑一个核相比,把 Redis 实例和物理核绑定,可以让主线程、子进程、后台线程共享使用 2 个逻辑核,可以在一定程序上环节 CPU 的资源竞争。但是,因为只是用了 2 个逻辑核,他们之间的 CPU 竞争仍然会存在。

方案二:优化 Redis 源码

这个方案就是通过修改 Redis 源码,把子进程和后台线程绑定到不同的 CPU 核上。
如果你对 Redis 的源码不太熟悉也没关系,因为这是通过编程实现绑核的一个通用做法。学会了这个方案,你可以在熟悉了源码之后把它用上。

通过编程实现绑核时,需要用到操作系统提供的 1 个数据结构 cpu_set_t 和 3 个函数 CPU_ZEROCPU_SETsched_setaffinity

  • cpu_set_t 数据结构:是一个位图,每一位用来表示服务器上的一个 CPU 逻辑核。
  • CPU_ZERO 函数:以 cpu_set_t 结构的位图为输入参数,把位图中所有的位设置为 0.
  • CPU_SET 函数:以 CPU 逻辑核编号和 cpu_set_t 位图为参数,把位图中和输入的逻辑核编号对应的位设置为1。
  • sched_setaffinity 函数:以进程 / 线程 ID 号 和 cpu_set_t 为参数,检查 cpu_set_t 中的哪一位为 1,就把输入的 ID 号所代表的进程 / 线程绑在对应的逻辑核上。

那么,怎么在编程时,把这个三个函数结合起来实现绑核呢?分四步走就行:

  1. 创建一个 cpu_set_t 结构的位图变量
  2. 使用 CPU_ZERO 函数,把 cpu_set_t 结构的位图所有的位都设置为 0 。
  3. 根据要绑定的逻辑核编号,使用 CPU_SET 函数,把 cpu_set_t 结构的位图相应位设置为 1.
  4. 使用 sched_setaffinity 函数,把程序绑定在 cpu_set_t 结构位图中为 1 的逻辑核上。

下面具体介绍下,分别把后台线程、子进程绑到不同的核上的做法。

先说后台线程。为了让你更好地理解编程实现绑核,你可以看下这段代码示例:

// 线程函数
void worker(int bind_cpu) {cpu_set_t cpuset; //创建位图变量CPU_ZERO(&cpuset); //位图变量所有位设置为0CPU_SET(bind_cpu, &cpuset); //绑程序绑定在 cpu_set_t 结构位图sche_setaffinity(0, sizeof(cpuset), &cpuset); // 把程序绑定在 cpu_set_t 结构位图//实际线程函数工作
}int main() {pthread_t pthread1;// 把线程的 pthread1 绑在编号为 3 的逻辑核上pthread_create(&pthread1, NULL, (void *)worker, 3)
}

对于 Redis 来说,它是在 bio.c 文件文件中的 bioProcessBackgroundJobs 函数中创建了后台线程。bioProcessBackgroundJobs 函数类似于刚刚的例子中的 worker 函数,在这个函数中实现绑核四步操作,就可以把后台线程绑定到主线程不同的核上了。

和给线程绑核类似,当我们使用 fork 创建子进程时,也可以把刚刚说的四步操作实现在 fork 后的子进程代码中,示例如下:

int main() {//用 fork 创建一个子进程pit_t p = fork();if(p < 0) {printf(" fork error\n");}//子进程代码部分else if() {cupt_set_t cpuset; //创建位图变量CPU_ZERO(&cpuset); //位图变量所有位设置为0CPU_SET(3, &cpuset); //把位图的第 3 位设置为1sche_setaffinity(0, sizeof(cpuset), &cpuset); // 把程序绑定在 3 号逻辑核//实际子进程工作exit(0);}...
}

对于 Redis 来说,生成 RDB 和 AOF 日志重写的子进程分别是下面两个文件的函数中实现的。

  • rdb.c 文件:rdbSaveBackground 函数
  • aof.c 文件:rewriteAppendOnlyFileBackground 函数。

这两个函数中都调用了 fork 创建子进程,所以,我们可以在子进程代码部分加上绑核的四步操作。

使用源码的优化方案,我们既可以实现 Redis 实例绑核,避免切换核带来的性能影响,还可以让子进程、后台线程和主线程在不同一个核上运行,避免了它们之间的 CPU 资源竞争。相比使用 taskset 绑核来说,这个方案可以进一步降低绑核的风险。

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

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

相关文章

【目标检测】对DETR的简单理解

【目标检测】对DETR的简单理解 文章目录 【目标检测】对DETR的简单理解1. Abs2. Intro3. Method3.1 模型结构3.2 Loss 4. Exp5. Discussion5.1 二分匹配5.2 注意力机制5.3 方法存在的问题 6. Conclusion参考 1. Abs 两句话概括&#xff1a; 第一个真正意义上的端到端检测器最…

实习日志10

1.用户信息 1.1.在用户管理中编辑用户信息 1.2.绑定公司id 1.3.显示在页面 2.修改识别逻辑 2.1.分析 先识别&#xff0c;再判断&#xff0c;清空键把识别结果清空 2.2.写码 修改了发票识别逻辑&#xff0c;略... 3.接高拍仪 3.1.js引入报错 分析&#xff1a; 遇到的错误…

MySQL数据库基础第一篇(SQL通用语法与分类)

文章目录 一、SQL通用语法二、SQL分类三、DDL语句四、DML语句1.案例代码2.读出结果 五、DQL语句1.DQL-基本查询2.DQL-条件查询3.DQL-聚合函数4.DQL-分组查询5.DQL-排序查询6.DQL-分页查询7.DQL语句-执行顺序1.案例代码2.读出结果 六、DCL语句1.DCL-管理用户2.DCL-权限控制1.案例…

鸿蒙开发-UI-页面路由

鸿蒙开发-UI-组件 鸿蒙开发-UI-组件2 鸿蒙开发-UI-组件3 鸿蒙开发-UI-气泡/菜单 文章目录 一、基本概念 二、页面跳转 1.router基本概念 2.使用场景 3.页面跳转参数传递 三、页面返回 1.普通页面返回 2.页面返回前增加一个询问框 1.系统默认询问框 2.自定义询问框 总…

【Mysql】数据库架构学习合集

目录 1. Mysql整体架构1-1. 连接层1-2. 服务层1-3. 存储引擎层1-4. 文件系统层 2. 一条sql语句的执行过程2-1. 数据库连接池的作用2-2. 查询sql的执行过程2-1. 写sql的执行过程 1. Mysql整体架构 客户端&#xff1a; 由各种语言编写的程序&#xff0c;负责与Mysql服务端进行网…

【安装记录】Chrono Engine安装记录

本文仅用于个人安装记录。 官方安装教程 https://api.projectchrono.org/8.0.0/tutorial_install_chrono.html Windows下安装 windows下安装就按照教程好了。采用cmake-gui进行配置&#xff0c;建议首次安装只安装核心模块。然后依此configure下irrlicht&#xff0c;sensor…

J-Link:STM32使用J-LINK烧录程序,其他MCU也通用

说明&#xff1a;本文记录使用J-LINK烧录STM32程序的过程。 1. J-LINK驱动、软件下载 1、首先拥有硬件J-Link烧录器。 2、安装J-Link驱动程序SEGGER 下载地址如下 https://www.segger.com 直接下载就可以了。 2.如何使用J-LINK向STM32烧写程序 1、安装好以后打开J-LINK Fl…

从零开始:CentOS系统下搭建DNS服务器的详细教程

前言 如果你希望在CentOS系统上建立自己的DNS服务器,那么这篇文章绝对是你不容错过的宝藏指南。我们提供了详尽的步骤和实用技巧,让你能够轻松完成搭建过程。从安装必要的软件到配置区域文件,我们都将一一为你呈现。无论你的身份是运维人员,还是程序员,抑或是对网络基础设…

【脑电信号处理与特征提取】P7-涂毅恒:运用机器学习技术和脑电进行大脑解码

运用机器学习技术和脑电进行大脑解码 科学研究中的大脑解码 比如2019年在Nature上一篇文章&#xff0c;来自UCSF的Chang院士的课题组&#xff0c;利用大脑活动解码语言&#xff0c;帮助一些患者恢复语言功能。 大脑解码的重要步骤 大脑解码最重要的两步就是信号采集和信号…

Coremail启动鸿蒙原生应用开发,打造全场景邮件办公新体验

1月18日&#xff0c;华为在深圳举行鸿蒙生态千帆启航仪式&#xff0c;Coremail出席仪式并与华为签署鸿蒙合作协议&#xff0c;宣布正式启动鸿蒙原生应用开发。作为首批拥抱鸿蒙的邮件领域伙伴&#xff0c;Coremail的加入标志着鸿蒙生态版图进一步完善。 Coremail是国内自建邮件…

浏览器——HTTP缓存机制与webpack打包优化

文章目录 概要强缓存定义开启 关闭强缓存协商缓存工作机制通过Last-Modified If-Modified-Since通过ETag If-None-Match 不使用缓存前端利用缓存机制&#xff0c;修改打包方案webpack 打包webpack 打包名称优化webpack 默认的hash 值webapck其他hash 类型配置webpack打包 web…

数据结构-内部排序

简介 排序&#xff08;Sorting&#xff09;&#xff1a;将一个数据元素&#xff08;或记录&#xff09;的任意序列&#xff0c;重新排列成一个按关键字有序的序列 排序算法分为内部排序和外部排序 内部排序&#xff1a;在排序期间数据对象全部存放在内存的排序 外部排序&am…

MySQL-运维-主从复制

一、概述 二、原理 三、搭建 1、服务器准备 2、主库配置 &#xff08;1&#xff09;、修改配置文件/etc/my.cnf &#xff08;2&#xff09;、重启MySQL服务器 &#xff08;3&#xff09;、登录mysql&#xff0c;创建远程链接的账号&#xff0c;并授予主从复制权限 &#xff0…

3593 蓝桥杯 查找最大元素 简单

3593 蓝桥杯 查找最大元素 简单 // C风格解法1&#xff0c;通过率100%&#xff0c;多组数据处理样式//str "abcdefgfedcba" //abcdefg(max)fedcba//str "xxxxx" //x(max)x(max)x(max)x(max)x(max)#include<bits/stdc.h>const int N 1e2 10;char …

分类预测 | Matlab实现SCN-Adaboost随机配置网络模型SCN的Adaboost数据分类预测/故障识别

分类预测 | Matlab实现SCN-Adaboost随机配置网络模型SCN的Adaboost数据分类预测/故障识别 目录 分类预测 | Matlab实现SCN-Adaboost随机配置网络模型SCN的Adaboost数据分类预测/故障识别分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现SCN-Adaboost随机配置网…

vue——实现多行粘贴到table事件——技能提升

最近在写后台管理系统时&#xff0c;遇到一个需求&#xff0c;就是要从excel表格中复制多行内容&#xff0c;然后粘贴到后台系统中的table表格中。 如下图所示&#xff1a;一次性复制三行内容&#xff0c;光标放在红框中的第一个框中&#xff0c;然后按ctrlv粘贴事件&#xff0…

C#,桌面游戏编程,数独游戏(Sudoku Game)的算法与源代码

本文包括以下内容&#xff1a; &#xff08;1&#xff09;数独游戏的核心算法&#xff1b; &#xff08;2&#xff09;数独游戏核心算法的源代码&#xff1b; &#xff08;3&#xff09;数独游戏的部分题目样本&#xff1b; &#xff08;4&#xff09;适老版《数独》的设计原则…

WordPress如何使用SQL实现一键关闭/开启评论功能(已有评论)

WordPress本人就自带评论功能&#xff0c;不过由于种种原因&#xff0c;有些站长不想开启评论功能&#xff0c;那么应该怎么实现一键关闭评论功能或开启评论功能呢&#xff1f;或者针对已有评论功能的文章进行一键关闭或开启评论功能应该怎么操作&#xff1f; 如果你使用的Wor…

Walrus 0.5发布:重构交互流程,打造开箱即用的部署体验

开源应用管理平台 Walrus 0.5 已于近日正式发布&#xff01; Walrus 0.4 引入了全新应用模型&#xff0c;极大程度减少了重复的配置工作&#xff0c;并为研发团队屏蔽了云原生及基础设施的复杂度。Walrus 0.5 在这一基础上&#xff0c;通过重构交互流程、增强抽象能力&#xff…

【tensorflow 版本 keras版本】

#. 安装tensorflow and keras&#xff0c; 总是遇到版本无法匹配的问题。 安装之前先查表 https://master--floydhub-docs.netlify.app/guides/environments/ 1.先确定你的python version 2.再根据下面表&#xff0c;确定安装的tesorflow, keras