Java 虚拟线程:案例研究

一. 关键要点

  • 虚拟线程是 Java 并发编程的一个重要进步,但在运行典型的云原生 Java 工作负载方面,它们并不比 Open Liberty 现有的自主线程池具有明显的优势。
  • 对于 CPU 密集型工作负载,由于目前尚不清楚的原因,虚拟线程的吞吐量低于 Open Liberty 的线程池。
  • 由于采用了每个请求一个线程的模型,虚拟线程从空闲到最大吞吐量的加速时间比 Open Liberty 的线程池更快。
  • Open Liberty 部署中的内存占用量会因应用程序设计、工作负载级别和垃圾收集行为等因素而有很大差异,因此虚拟线程占用量的减少可能不会导致内存使用量的整体减少。
  • 虚拟线程在某些用例中表现出一些意外的性能问题,Java 开发人员应该注意这一点。

JDK 21 的发布带来了一个广为人知的新功能——Java 虚拟线程。该功能标志着 Java 开发人员在更好地处理应用程序中的并行性方面取得了重大飞跃。Java 虚拟线程的一些目标包括:

  • 轻量级、可扩展且用户友好的并发模型
  • 高效利用系统资源
  • “大大减少编写、维护和观察高吞吐量并发应用程序的工作量”(JEP425)

虚拟线程引起了 Java 开发者社区的极大兴趣,其中包括应用程序框架,例如Open Liberty,这是一个开源、模块化、云原生的 Java 应用程序运行时。评估了这项新的 Java 功能是否可以为我们的用户带来好处,甚至可能取代 Liberty 应用程序运行时本身使用的当前线程池逻辑。至少,我们希望更好地了解虚拟线程技术及其性能,以便我们能够为 Liberty 用户提供明智的指导。

本文报告了我们的发现。其中包括:

  • Java 虚拟线程实现的概述。
  • 当前 Liberty 线程池技术的概述。
  • 我们对一些绩效指标进行了评估,包括一些意外的观察结果。
  • 我们的研究结果的摘要。

二. Java 虚拟线程

虚拟线程首次在 JDK 19 中引入,在 JDK 20 中得到增强,并在 JDK 21 中最终确定(如JDK 增强提案 (JEP) 444中所述)。

过去,Java 开发人员使用“每个请求一个线程”模型实现应用程序,其中每个请求在其生命周期内由专用线程处理。这些线程(称为平台线程)作为操作系统线程(OS 线程)的包装器实现。但是,OS 线程占用大量系统内存并由 OS 层调度,随着部署的线程越来越多,这可能会导致扩展问题。

虚拟线程的主要动机之一是保持线程请求模型的简单性,同时避免专用操作系统线程的高成本。虚拟线程通过最初将每个线程创建为 Java 堆上的轻量级对象,并仅在需要时使用操作系统线程,最大限度地减少了此问题。这种操作系统线程的“共享”可以更好地利用系统资源。从理论上讲,这是虚拟线程的一个优势:开发人员现在可以在单个 JVM 中有效地使用“数百万个线程”。

下图显示了 Java 虚拟线程和 OS 线程之间的多对一关系,然后这些线程被调度在 CPU 级别运行。

三. Open Liberty 的自主线程池

Open Liberty 的共享线程池方法还最大限度地降低了专用 OS 线程的高成本。Liberty 使用共享线程(称为“Liberty 线程池”)来执行应用程序业务逻辑功能,并为 I/O 功能分配单独的线程。此外,Liberty 线程池具有自适应性,并且可以自动调整大小(如本文所述)。对于大多数用例,无需进行额外调整,但可以选择配置最小和最大池大小。

与 Web 服务器(例如使用虚拟线程实现的 Helidon Web 服务器)不同,Liberty 之类的应用程序运行时不仅仅是建立 I/O 连接,然后长时间处于空闲状态。在 Liberty 上运行的应用程序通常会执行大量业务逻辑,这需要 CPU 资源。Liberty 部署通常不会使用数千或数百万个线程,因为 CPU 资源被几百个线程(或更少)完全消耗,尤其是在仅分配了几个甚至一小部分 CPU 的容器或 pod 中。

四. 性能测试

我们的评估主要侧重于 Liberty 客户常用的用例和配置。我们使用现有的基准测试应用程序来比较 Liberty 的线程池和虚拟线程的相对性能。这些基准测试应用程序使用 REST 和 MicroProfile,并在事务期间执行一些基本的业务逻辑。

我们的目标是模拟如果我们用虚拟线程替换 Liberty 中的自主线程池,大多数 Liberty 用户将看到的情况。因此,我们的评估主要侧重于具有 10 到 100 个线程的配置。但是,我们扩展了评估范围,还比较了 Liberty 的线程池和具有几千个线程的虚拟线程行为,因为使用多线程运行是虚拟线程的一大优势。

为了评估执行虚拟线程卸载和挂载操作的用例,我们使用了一款在线银行模拟应用,该应用会向远程系统发出请求,并在可配置的延迟后做出响应。延迟响应意味着测试系统中的线程在 I/O 上被阻塞,并且在一段时间内未被 CPU 使用。此应用会生成允许在事务中途卸载虚拟线程,然后在收到远程系统的回复后重新挂载的工作类型(即,它允许共享操作系统线程)。

1. 测试用例环境

我们使用Eclipse Temurin(带有 HotSpot JVM 的 OpenJDK)和IBM Semeru Runtimes(带有 OpenJ9 JVM 的 OpenJDK)运行了这些性能测试。我们观察到 Liberty 的线程池和虚拟线程在这两个 JDK 上的性能差异相似。除非另有说明,否则下面显示的结果都是在运行 Liberty 23.0.0.10 GA 和 Temurin 21.0.1_12 版本时生成的。

免责声明:我们对虚拟线程的评估侧重于如果使用上述“每个请求一个线程”模型实现虚拟线程来取代自主线程池,Liberty 用户是否会获得性能优势。在阅读测试用例时,需要牢记这一重要背景,因为对于没有像 Liberty 那样具有自调节线程池的其他应用程序运行时,结果可能会完全不同。

测试案例 1:CPU 吞吐量

**目标:**评估 CPU 吞吐量以发现使用虚拟线程与 Liberty 的线程池时是否存在性能损失。

**发现:**对于某些配置,使用虚拟线程时工作负载的吞吐量比使用 Liberty 的线程池时低 10-40%。

在本测试中,我们运行了多个 CPU 密集型应用程序,并比较了在给定数量的 CPU 上(使用虚拟线程与 Liberty 的线程池运行)每秒可以完成多少事务 (tps)。我们使用Apache JMeter来驱动各种负载,以使小型系统达到越来越高的 CPU 利用率。

在一个示例中,我们以短暂的 2 毫秒延迟运行网上银行应用程序,以便虚拟线程功能(在 OS 线程上挂载/卸载/重新挂载)在每个单独的任务上执行,而应用程序整体仍然相当占用 CPU。负载逐渐增加,在每个负载级别运行足够长的时间(150 秒),以获得稳定的平均吞吐量测量值。

在低负载水平下,网上银行应用程序的虚拟线程吞吐量大致等于 Liberty 的线程池吞吐量(见图),虚拟线程使用的 CPU 稍多一些(未显示 CPU 利用率)。随着负载的增加,使用虚拟线程的每秒交易量逐渐落后于 Liberty 的线程池。


我们预计虚拟线程在这种 CPU 密集型应用程序中可能会稍微慢一些,因为虚拟线程不会使代码的运行速度比在传统 Java 平台线程上运行的速度更快,并且虚拟线程会产生一些开销,包括:

  • 挂载和卸载:虚拟线程挂载在平台线程上以在阻塞点和执行完成时运行和卸载。此外,每次挂载或卸载操作都会发出JVM 工具接口(JVMTI) 通知。这些操作很轻量,但并非零成本。
  • 垃圾收集:每次事务都会创建并丢弃一个虚拟线程对象,并产生分配和垃圾收集成本。
  • 线程链接上下文丢失:Liberty 使用ThreadLocal变量在请求之间共享公共信息。使用池化线程时,这种方法的效率会因虚拟线程而降低,因为ThreadLocal虚拟线程会消失。作为该项目的一部分,我们将主要ThreadLocal用途转换为其他非线程链接共享机制,但仍存在一些影响较小的实例。

然而,CPU 分析表明,这些可能的虚拟线程开销都不足以解释观察到的吞吐量差异。我们将在后面的“意外的虚拟线程性能发现”部分讨论其他可能的原因。

对于少数 CPU 上的 CPU 密集型应用程序(Liberty 的典型用例),与在 Liberty 线程池中的常规 Java 平台线程上运行相同代码相比,虚拟线程并没有使 Java 代码的执行速度更快。

测试案例 2:启动时间

**目标:**量化虚拟线程与 Liberty 的线程池相比达到完全吞吐量的速度。

**发现:**当突然施加重负载时,在虚拟线程上运行的应用程序达到最大吞吐量的速度明显快于在 Liberty 的线程池上运行时。

虚拟线程使用的简单模型是,每个任务都有自己的(虚拟)线程来运行,因此我们的 Liberty 虚拟线程原型启动了一个新的虚拟线程来执行从负载驱动程序收到的每个任务。因此,使用虚拟线程,每个任务都会立即有一个线程可以运行,而使用 Liberty 的线程池,任务可能必须等待线程可用。

为了充分测试此场景,我们需要运行具有足够长响应延迟的在线银行应用程序,以导致数千个并发交易使 CPU 饱和。此工作负载需要数千个线程来处理交易,无论是每个交易的虚拟线程还是 Liberty 线程池中的传统 Java 平台线程。

处理 Liberty 线程池中的数千个线程

我们发现 Liberty 的线程池在几千个线程的情况下运行良好。由于各种虚拟线程讨论中都提到了使用许多平台线程的问题,因此我们一直在寻找 Liberty 线程池中出现问题的迹象。例如,它在处理几千个线程时可能会变得不稳定,或者出现其他“线程过多”问题的迹象。我们没有看到此类问题。

相反,我们发现 Liberty 线程池的吞吐量实际上比虚拟线程略快 (2-3%)。Liberty 线程池的 CPU 使用率降低了约 10%,而 Liberty 线程池的每 CPU 事务利用率提高了 12-15%(主要是由于自主控制的设计决定了 Liberty 的线程池大小)。Liberty 线程池的自主控制允许池在工作负载需要时增长到数千个线程,同时保持稳定运行。

使用 Liberty 线程池与虚拟线程的启动时间

在扩展评估中,虚拟线程从低负载到满负荷的上升时间非常快。Liberty 的线程池上升速度较慢,因为它会根据观察到的吞吐量逐渐调整;Liberty 的线程池以 1500 毫秒的间隔决定是增加、缩小还是保持相同的大小,并且需要数十分钟才能逐渐决定应添加越来越多的线程来处理提供的负载。

经过这次测试,我们修改了 Liberty 的线程池自主性,以便在有更多空闲 CPU 资源可用且 Liberty 线程池请求队列较深时更积极地扩大线程池。通过此修复(在Open Liberty 23.0.0.10 及更高版本中可用),当在 Liberty 线程池上运行的在线银行应用程序突然受到重负载(超过 30 秒)时,该应用程序现在仅在虚拟线程上运行同一应用程序后约 20-30 秒(而不是数十分钟)即可达到峰值吞吐量,即使工作负载需要空闲 JVM 上大约 6000 个线程(见图)。虚拟线程原型仍然能够更快地启动,因为它在到达时为每个请求提供一个新的虚拟线程,但虚拟线程和 Liberty 线程池之间的加速差异已大大缩小。

测试用例3:内存占用

**目标:**确定在恒定负载下,Java 进程(包括虚拟线程和 Liberty 的线程池)使用了多少内存。

**发现:**虚拟线程较小的每个线程占用空间在需要几百个线程的配置中仅具有相对较小的直接影响,并且可能会被 JVM 中其他内存使用的影响所抵消。

虚拟线程使用的内存(Java 进程大小)比传统平台线程少,因为它们不需要专用的后备操作系统线程。此测试用例测量了虚拟线程的这种每线程内存优势如何转化为典型 Liberty 工作负载级别下 JVM 的总内存使用量。我们发现了一组相当复杂的结果。

我们原本以为使用虚拟线程运行的负载会比使用 Liberty 线程池运行相同负载时占用更少的内存。但我们发现,有时虚拟线程配置占用的内存较少,但有时占用的内存较多。

这种变化的出现是因为线程实现以外的因素也影响了 Java 进程的内存使用。在我们的测试中,对内存使用变化产生重大影响的一个因素是 DirectByteBuffers (DBB),它是 Java 网络基础架构的一部分。(有关 Direct ByteBuffers 的背景知识,请参阅ByteBuffer API 。)

DirectByteBuffers 是一种由两部分组成的结构,堆上有一个小型 Java 引用对象,本机或堆外区域中有一个大小可变(通常大得多)的内存区域。Java 引用对象在不再需要后被释放并被垃圾回收,之后关联的本机内存被清除。如果 DirectByteBuffers 引用对象存活的时间足够长,可以提升到旧代区域(在典型的 Java 分代 GC 模型中),则本机内存分配将保留到全局 GC。由于全局 GC(根据设计)并不频繁,因此这种分配和保留模式可能会导致 Java 进程占用空间的增长显著大于活动运行时使用量。

注意:此测试在最小堆大小较小和最大堆大小相对较大的情况下运行。这是为了让堆内存使用率的变化明显成为影响 JVM 总内存使用率的因素之一。

在某些情况下,使用虚拟线程运行的负载比使用 Liberty 线程池运行的负载占用更多内存,我们发现差异归因于 DirectByteBuffers 保留。这并不表示虚拟线程存在问题:DirectByteBuffers 内存保留多长时间取决于多个因素的相互作用,包括事务持续时间、Java 堆临时内存大小和保有权提升时间。我们可以使用略有不同的配置或调整运行相同的测试,并使虚拟线程使用的内存少于 Liberty 线程池,差异来自 DirectByteBuffers 保留。

例如,工作负载略微增加 10% 就会导致在 Liberty 线程池上运行的网上银行应用程序使用的内存减少 25%,但导致在虚拟线程上运行的同一应用程序使用的内存增加 185%(参见图表)。


避免为每个虚拟线程分配一个操作系统线程可以显著减少本机内存,但与应用程序运行时使用的其他内存相比,这可能相对较小。在只需要几百个线程的配置中,使用虚拟线程带来的本机内存减少可能会被其他难以预测的影响所掩盖,例如 Java 堆的增长速度和释放相关本机内存的垃圾收集的及时性,例如 DirectByteBuffers。

在性能工作中,人们常说“YMMV”(“你的里程可能会有所不同”)这句话。有些虚拟线程用户会发现系统的总内存使用量减少,而有些用户则会看到增加。内存使用量的变化中,只有一小部分可归因于虚拟线程。

意外的虚拟线程性能发现

我们对虚拟线程的调查涉及对基准应用程序进行的许多实验,改变了 CPU 数量、负载量、远程延迟(对于网上银行应用程序)、堆大小等。这些实验产生了一些非常出乎意料的发现,这些发现与前面的部分不太吻合。

具体来说,在两个 CPU 上运行短时间任务时,我们有时会发现虚拟线程的性能非常差。我们将其追溯到 Linux 内核调度程序与 Java 的 ForkJoinPool 线程管理的交互方式。较新版本的 Linux 内核调度程序改变了与 ForkJoinPool 的交互方式,但我们仍然发现虚拟线程的性能很差,只是方式不同。虚拟线程用户可能会遇到类似的问题,应该注意,升级到较新的 Linux 内核只会改变行为,而不是修复它。

在此测试中,我们使用了 MicroProfile 基准测试应用程序 mp-ping,它对 REST 服务执行简单的“ping”。负载驱动程序在 Liberty 上运行的 mp-ping 应用程序上点击 REST URL,并立即收到“ping”响应(0.05-0.10 毫秒)。

在虚拟线程上运行时吞吐量低且 CPU 低

我们发现,在虚拟线程上运行 2-CPU 配置的短时任务 (mp-ping) 产生的吞吐量比在 Liberty 线程池上运行的吞吐量低得多,因此 CPU 利用率也较低。虚拟线程上的吞吐量低至 Liberty 线程池吞吐量的 50-55%,如下图所示。

在执行持续时间较长的任务时(最长可达 1 毫秒),性能也会较差,但使用更多 CPU 时,情况会好一些,只是不那么严重。

我们在具有不同 Linux 内核级别的多个不同硬件平台上重现了虚拟线程的低吞吐量和低 CPU 利用率问题,以确保该行为不是原始测试系统上的某些怪癖造成的。我们还创建了一个简单的独立应用程序,该应用程序生成在可配置时间段内消耗 CPU 的任务,它显示了类似的低吞吐量和低 CPU 利用率行为,因此性能不佳不是由 Liberty 造成的。

ForkJoinPool 和 Linux 内核调度程序

对虚拟线程性能不佳的根本原因进行调查后发现,Java 的 ForkJoinPool(用于管理支撑虚拟线程的平台线程)在有大量工作需要完成时会将其中一个平台线程暂停 10-13 毫秒。如果一个平台线程暂停,虚拟线程就无法及时运行,从而导致我们观察到的低吞吐量和低 CPU 利用率。

进一步的调查表明 Linux 线程调度程序存在问题:跟踪显示 ForkJoinPool 代码中调用了取消停放的平台线程,但并未立即取消停放。我们得出结论,性能不佳是由 Linux 线程调度程序和 ForkJoinPool 工作线程管理之间的交互引起的。这种交互对 Liberty 的线程池来说不是问题,因为它不使用 ForkJoinPool 来管理平台线程。

我们尝试了可用的 ForkJoinPool 调整选项、Linux 调度程序调整选项以及对 ForkJoinPool 实现的各种修改,产生了一些小的性能改进,但并没有显著缩小与 Liberty 线程池性能的差距。

注意:我们的调查显示,对于我们在 4.18 Linux 内核中发现的虚拟线程问题,使用 2 个 CPU 运行可能是最糟糕的情况。在具有 1 个 CPU 或 4 个或更多 CPU 的测试系统上运行相同的工作负载时,性能问题仍然存在,但不那么突出。

运行虚拟线程时吞吐量低且 CPU 使用率高

前两节描述的测试主要针对 Linux 内核 4.18,这是 Red Hat Enterprise Linux(RHEL)8 中当前可用的内核。当我们在较新的 Linux 内核 5.14(RHEL 9)和内核 6.2(Ubuntu 22.04)上运行相同的测试时,我们发现虚拟线程存在不同的性能问题。

使用较新的 Linux 内核,在虚拟线程上运行 mp-ping 应用程序产生的吞吐量仍然比 Liberty 线程池略低,但 CPU 利用率更高。随着负载的增加,虚拟线程的吞吐量比 Liberty 线程池的吞吐量低 20-30%,如下图所示。


这些发现表明,对于某些工作负载,虚拟线程可能存在不同的性能问题,这取决于 Linux 内核级别。

调查这些行为原因的后续步骤

我们与 OpenJDK 社区成员讨论了这些发现,并将继续与他们一起研究和测试修改。两个图表中显示的运行均使用了 Temurin 22 的最新夜间版本,以利用最新版本的 ForkJoinPool(目前正在修订中),以防 ForkJoinPool 修订版纠正了我们最初在 Temurin 21 中观察到的问题(但事实并非如此)。

需要进一步调查才能完全确定根本原因并找到解决方案,我们正在积极与 OpenJDK 社区合作。我们非常感谢Doug Lea(Java 并发工作领域的领导者和 ForkJoinPool 类的作者)和 OpenJDK 社区的其他成员帮助我们调查这些虚拟线程性能问题。我们在此报告这些问题,以便提醒可能遇到类似问题的虚拟线程用户,具体取决于他们的虚拟线程用例。

总结和结论

我们使用一些代表 Liberty 典型客户用途的简单应用程序研究了虚拟线程的性能,以及三个主要性能方面:

  • 吞吐量:在我们尝试的应用中,虚拟线程的性能比 Liberty 的线程池差。根据 CPU 数量、任务持续时间、Linux 内核级别和 Linux 调度程序调整的不同,这种糟糕的性能在不同级别上都有所体现。
  • 加速:当工作负载突然增加,且任务持续时间较长且需要许多线程时,虚拟线程会比 Liberty 的线程池更快地达到满吞吐量,但这种优势很快就会消失。
  • 内存占用:在需要几百个线程的配置中,虚拟线程较小的每个线程占用空间的影响相对较小,并且可能会被 JVM 中其他内存使用的影响所抵消。

此外,我们惊讶地发现,在某些用例中,在虚拟线程上运行时存在性能问题。我们将此问题追溯到 Linux 内核调度程序与 Java 的 ForkJoinPool 线程管理之间的交互。即使使用较新版本的内核,此问题仍然存在,尽管方式有所不同。

在将 Liberty 现有的线程管理与新的 Java 虚拟线程进行比较后,我们发现,现有的 Liberty 线程池在中等高(1000 个线程)并发级别下为 Liberty(以及在 Liberty 上运行的任何应用程序)提供相当或通常更好的性能。虽然与 Liberty 的线程池相比,虚拟线程可以在更高的并发级别上显示出优势,但这取决于正确的条件、高任务延迟、大量 CPU 或这些因素的组合。

Java 应用程序开发人员仍然可以在自己的 Liberty 上运行的应用程序中 使用虚拟线程,但我们暂时决定不使用虚拟线程替换 Liberty 线程池。如本文前面所述,在很多用例中,虚拟线程可能非常有用,有助于简化多线程应用程序的开发。但是,如上所述,开发人员还应该注意某些类型的应用程序中的一些问题。通过在本文中分享我们的经验,我们希望 Java 开发人员能够更好地了解何时以及是否在自己的应用程序中实现虚拟线程。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Idea如何快速高效的修改项目的包名

文章目录 前言一、全局替换的快捷键二、弹出如下的界面 前言 当我们有时候在做项目迁移的时候,需要快速的修改项目的包名!那么如何快速高效的修改项目的报名呢? 经过尝试了很多方法!最简单的方法就是利用全局替换来直接替换报名&…

QT实现带动态弹出动画的自定义通知提示框

Qt中经常会用到提示框,用于交互操作!QMessageBox是被大多数人用到的,用起来是很方便,但是控件类型、大小、布局、样式、往往不是开发者想要的。本实例实现的Notification控件,是一种悬浮在角落的通知提醒框。 一、简述…

Day07-ES集群加密,kibana的RBAC实战,zookeeper集群搭建,zookeeper基本管理及kafka单点部署实战

Day07-ES集群加密,kibana的RBAC实战,zookeeper集群搭建,zookeeper基本管理及kafka单点部署实战 0、昨日内容回顾:1、基于nginx的反向代理控制访问kibana2、配置ES集群TSL认证:3、配置kibana连接ES集群4、配置filebeat连接ES集群5、配置logsta…

Mysql-错误处理: Found option without preceding group in config file

1、问题描述 安装MYSQL时,在cmd中“初始化”数据库时,输入命令: mysqld --initialize --consolecmd报错: D:\mysql-5.7.36-winx64\bin>mysql --initialize --console mysql: [ERROR] Found option without preceding group …

打印室预约小程序的设计

管理员账户功能包括:系统首页,个人中心,用户管理,附近打印店管理,文件打印管理,当前预约管理,预约历史管理,打印记录管理 开发系统:Windows 架构模式:SSM JD…

linux服务器如何创建Raid10阵列,删除raid10

文章目录 1,首先查看一下机器上有几块盘2,构建raid10阵列3,把制作好的 RAID 磁盘阵列格式化为 ext4 格式4,创建挂载点然后把硬盘设备进行挂载操作5,查看/dev/md0 磁盘阵列的详细信息6,删除raid10 1&#xf…

理解深度学习中的过拟合和Dropout

新书速览|PyTorch深度学习与企业级项目实战-CSDN博客 随着迭代次数的增加,我们可以发现测试数据的loss值和训练数据的loss值存在着巨大的差距, 如图4-8所示,随着迭代次数的增加,training loss越来越好,但test loss却越…

分布式缓存-Redis持久化

使用缓存的时候,我们经常需要对内存中的数据进行持久化(将内存中的数据写入到硬盘中)。 原因:重用数据(比如重启机器、机器故障之后恢复数据),做数据同步(比如 Redis 集群的主从节点…

广告投放的智能优化:Kompas.ai如何提高广告效果

在数字广告领域,智能优化已成为提升广告投放效果和投资回报率(ROI)的关键。Kompas.ai,一款先进的广告智能优化工具,利用数据分析和机器学习技术,帮助广告主实现更精准、高效的广告投放。 智能优化在提升广告效果中的作用 智能优化…

微调 Florence-2 - 微软的尖端视觉语言模型

Florence-2 是微软于 2024 年 6 月发布的一个基础视觉语言模型。该模型极具吸引力,因为它尺寸很小 (0.2B 及 0.7B) 且在各种计算机视觉和视觉语言任务上表现出色。 Florence 开箱即用支持多种类型的任务,包括: 看图说话、目标检测、OCR 等等。虽然覆盖面…

MySQL字符串魔法:拼接、截取、替换与定位的艺术

在数据的世界里,MySQL作为一把强大的数据处理利剑,其字符串处理功能犹如魔术师手中的魔法棒,让数据变换自如。今天,我们就来一场关于MySQL字符串拼接、截取、替换以及查找位置的奇幻之旅,揭开这些操作的神秘面纱。 介绍…

谷歌浏览器114之前、126、127、128版本驱动下载,实时更新

114之前版本下载链接在这里 126以后版本下载链接在此,只有后面status是绿色对勾的才可以下载,**驱动大版本一致就可以使用,不需版本号一模一样;**下载所需版本只需点击对应的版本名称即可跳转到对应版本的下载位置。 以正式版为例…

FullCalendar日历组件集成实战(20)

背景 有一些应用系统或应用功能,如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件,但功能比较简单,用来做数据展现勉强可用。但如果需要进行复杂的数据展示,以及互动操作如通过点击添加事件&#xff0…

C# modbus 图表

控件:chart1(图表),cartesianChart1(第三方添加图表),timer(时间) 添加第三方: 效果:图标会根据连接的温度,湿度用timer时间进行改变 Chart1控件样式:Series添加线条,颜色&#xf…

编程从零基础到进阶(更新中)

题目描述 依旧是输入三个整数,要求按照占8个字符的宽度,并且靠左对齐输出 输入格式 一行三个整数,空格分开 输出格式 输出它们按格式输出的效果,占一行 样例输入 123456789 -1 10 样例输出 123456789-1 10 #include "stdio.…

which 命令在Linux中是一个快速查找可执行文件位置的工具

文章目录 0、概念1、which --help2、which命令解释 0、概念 which命令用于查找命令的可执行文件的路径which 命令在 Linux 中用于查找可执行命令的完整路径。当你在 shell 中输入一个命令时,shell 会在环境变量 $PATH 定义的目录列表中查找这个命令。which 命令可以…

基于Python+Flask+SQLite的豆瓣电影可视化系统

FlaskMySQLEcharts 基于PythonFlaskSQLite的豆瓣电影可视化系统 Echarts 不支持登录注册,并且信息存储在数据库中 不含爬虫代码,或爬虫代码已失效 简介 基于PythonFlaskMySQL的豆瓣电影可视化系统,采用Echart构建图表,支持自定…

ARM架构与FreeRTOS中的内存管理(flash与SRAM,堆栈)

在ARM架构中,内存的地址空间是如何划分的,内存映射表是怎样的 在Cortex-M7中,存储器一共有4GB的地址空间,4GB的地址空间又被划分为8个区域块,每个块有512M的内存。 Note:4GB的地址空间为 0x0000 0000 - 0…

[C++] 深度剖析C_C++内存管理机制

文章目录 内存分布内存分布图解 C语言中动态内存管理方式malloc:callocrealloc C内存管理方式内置类型**自定义类型** operator new & operator deleteoperator new & operator delete函数operator newoperator delete **new T[N]** 与**delete[]** **定位new表达式(pl…

第二章 UI组件【Android基础学习】

第二章 UI组件【Android基础学习】 前言版权推荐开源第二章 UI组件2-1 布局管理器2-1-1 LinearLayout2-1-2 RelativeLayout 2-2 TextView2-3 Button2-4 EditText2-5 RadioButton2-6 复选框CheckBox2-7 ImageView2-8 列表视图 ListView2-9 网格视图 GridView2-10 滚动视图 Scrol…