阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

解密新一代 Java JIT 编译器 Graal

  • 2018-07-26
  • 本文字数:15440 字

    阅读完需:约 51 分钟

关键要点

  • Java 的 C2 JIT 编译器寿终正寝。
  • 新的 JVMCI 编译器接口支持可插拔编译器。
  • 甲骨文开发了 Graal,一个用 Java 编写的 JIT,作为潜在的编译器替代方案。
  • Graal 也可以独立运行,是新平台的主要组件。
  • GraalVM 是下一代 VM,支持多种语言(不仅仅是那些可编译为 JVM 字节码的语言)。

甲骨文的 Java 实现是基于开源的 OpenJDK 项目,其中包括自 Java 1.3 以来一直存在的 HotSpot 虚拟机。HotSpot 包含两个独立的 JIT 编译器,分别是 C1 和 C2(有时称为“客户端”编译器和“服务器端”编译器),现在的 Java 通常会在运行程序期间同时使用这两个 JIT 编译器。

Java 程序首先在解释模式下启动,在运行了一段时间之后,经常被调用的方法会被识别出来,并使用 JIT 编译器进行编译——先是使用 C1,如果 HotSpot 检测到这些方法有更多的调用,就使用 C2 重新编译这些方法。这种策略被称为“分层编译”,是 HotSpot 默认采用的方式。

对于大多数 Java 应用程序来说,C2 编译器是整个运行环境中最重要的一个部分,因为它为程序中最重要的部分代码生成了高度优化的机器码。

C2 非常成功,可以生成与 C++ 相媲美(甚至比 C++ 更快)的代码,这要归功于 C2 的运行时优化,而这些在 AOT(Ahead of Time)编译器(如 gcc 或 Go 编译器)中是没有的。

不过,近年来 C2 并没有带来多少重大的改进。不仅如此,C2 中的代码变得越来越难以维护和扩展,新加入的工程师很难修改使用 C++ 特定方言编写的代码。

事实上,人们(Twitter 等公司以及像 Cliff Click 这样的专家)普遍认为,在当前的基础上根本不可做出重大的改进。也就是说,任何后续的 C2 改进都是微不足道的。

在最近发布的版本中有一些改进,比如使用了更多的 JVM 内联函数(intrinsic),文档中是这样描述的这项技术的(主要用于描述 @HotSpotIntrinsicCandidate 注解):

如果 HotSpot VM 使用手写汇编或手写编译器 IR(一种旨在提升性能的编译器内联函数)替换带注解的方法,那么这个方法就是内联的。

JVM 在启动时会探测它运行在哪个处理器上,因此 JVM 可以准确地知道 CPU 支持哪些特性。它创建了一个特定于当前处理器的内联函数表,也就是说 JVM 可以充分利用硬件的能力。

这与 AOT 编译不同,后者在编译时考虑的是通用芯片,并对可用的特性做出保守的假设,因为如果 AOT 编译的二进制文件在运行时试图执行当前 CPU 不支持的指令,就会崩溃。

HotSpot 已经支持了不少内联函数——例如众所周知的 Compare-And-Swap(CAS)指令,可用于实现原子整数等功能。在几乎所有的现代处理器上,这都是通过单个硬件指令来实现的。

JVM 预先知道这些内联函数,并依赖于操作系统或 CPU 架构对特定功能的支持。因此,它们特定于平台,并非每个平台都支持所有的内联函数。

一般来说,内联函数应该被视为点修复,而不是一种通用技术。它们具有强大、轻量级和灵活的优点,但要支持多种架构,带来了潜在的高开发和维护成本。

因此,尽管在内联函数方面取得了进展,但不管怎样,C2 已经走到了生命的尽头,必须被替换掉。

甲骨文最近宣布推出第一版 GraalVM ,这是一个研究项目,可能会成为 HotSpot 的替代方案。

Java 开发人员可以认为 Graal 是由几个独立但互相关联的项目组成的——它既是 HotSpot 的新型 JIT 编译器,也是一个新的多语言虚拟机。我们使用 Graal 来称呼这个新的编译器,使用 GraalVM 来称呼这个新虚拟机。

Graal 的总体目标是重新思考如何更好地编译 Java(以及 GraalVM 支持的其他语言)。Graal 最初的出发点非常简单:

Java 的(JIT)编译器将字节码转换为机器码——在 Java 中,只不过是从一个 byte[] 到另一个 byte[] 的转换——那么如果转换代码是用 Java 编写的话会怎样呢?

事实证明,用 Java 编写编译器有如下的一些优点:

  • 工程师开发新编译器的进入门槛要低得多。
  • 编译器的内存安全性。
  • 能够利用成熟的 Java 工具进行编译器开发。
  • 更快的新编译器功能原型设计。
  • 编译器可以独立于 HotSpot。
  • 编译器能够自己编译自己,以生成更快的 JIT 编译版本。

Graal 使用了新的 JVM 编译器接口(JVMCI,对应 JEP 243 ),可以用在 HotSpot 中,也可以作为 GraalVM 的主要组成部分。Graal 已经发布,尽管它在 Java 10 中仍然是处于实验性阶段。要切换到新的 JIT 编译器,可以这样做:

-XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler

我们可以通过三种不同的方式运行一个简单的程序——使用常规的分层编译器,或者使用 Java 10 上的 Graal,或者使用 GraalVM 本身。

为了展示 Graal 的效果,我们使用了一个简单的例子,它可以长时间运行,这样就看到编译器的启动过程——进行简单的字符串哈希:

package kathik;

public final class StringHash {

    public static void main(String[] args) {
        StringHash sh = new StringHash();
        sh.run();
    }

    void run() {
        for (int i=1; i<2_000; i++) {
            timeHashing(i, 'x');
        }
    }

    void timeHashing(int length, char c) {
        final StringBuilder sb = new StringBuilder();
        for (int j = 0; j < length  * 1_000_000; j++) {
            sb.append(c);
        }
        final String s = sb.toString();
        final long now = System.nanoTime();
        final int hash = s.hashCode();
        final long duration = System.nanoTime() - now;
        System.out.println("Length: "+ length +" took: "+ duration +" ns");
    }
}

我们可以设置 PrintCompilation 标记来执行此代码,这样就可以看到被编译的方法(它还提供了一个基线,可与 Graal 运行进行比较):

java -XX:+PrintCompilation -cp target/classes/ kathik.StringHash > out.txt

要查看 Graal 在 Java 10 上运行的效果:

java -XX:+PrintCompilation \
     -XX:+UnlockExperimentalVMOptions \
     -XX:+EnableJVMCI \
     -XX:+UseJVMCICompiler \
     -cp target/classes/ \
     kathik.StringHash > out-jvmci.txt

对于 GraalVM:

java -XX:+PrintCompilation \
     -cp target/classes/ \
     kathik.StringHash > out-graal.txt

这些将生成三个输出文件——前 200 次调用 timeHashing() 后生成的输出看起来像这样:

$ ls -larth out*
-rw-r--r--  1 ben  staff    18K  4 Jun 13:02 out.txt
-rw-r--r--  1 ben  staff   591K  4 Jun 13:03 out-graal.txt
-rw-r--r--  1 ben  staff   367K  4 Jun 13:03 out-jvmci.txt

正如预期的那样,Graal 会产生更多的输出——这是由于 PrintCompilation 输出的不同。不过这一点也不足为奇——Graal 首先要编译 JIT 编译器,所以在 VM 启动后的前几秒内会有大量的 JIT 编译器预热动作。

让我们看一下在 Java 10 上使用 Graal 编译器的 JIT 输出(常规的 PrintCompilation 格式):

$ grep graal out-jvmci.txt | head
    229  293       3       org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory::adjustCompilationLevelInternal (70 bytes)
    229  294       3       org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory::checkGraalCompileOnlyFilter (95 bytes)
    231  298       3       org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory::adjustCompilationLevel (9 bytes)
    353  414   !   1       org.graalvm.compiler.serviceprovider.JDK9Method::invoke (51 bytes)
    354  415       1       org.graalvm.compiler.serviceprovider.JDK9Method::checkAvailability (37 bytes)
    388  440       1       org.graalvm.compiler.hotspot.HotSpotForeignCallLinkageImpl::asJavaType (32 bytes)
    389  441       1       org.graalvm.compiler.hotspot.word.HotSpotWordTypes::isWord (31 bytes)
    389  443       1       org.graalvm.compiler.core.common.spi.ForeignCallDescriptor::getResultType (5 bytes)
    390  445       1       org.graalvm.util.impl.EconomicMapImpl::getHashTableSize (43 bytes)
    390  447       1       org.graalvm.util.impl.EconomicMapImpl::getRawValue (11 bytes)

像这样的小实验应该谨慎对待。例如,太多的屏幕 IO 可能会影响预热性能。不仅如此,随着时间的推移,为不断增加的字符串分配的缓冲区将会变得越来越大,以至于必须在 Humongous Region(G1 回收器为大对象保留的特殊区域)中进行分配——Java 10 和 GraalVM 默认使用了 G1 回收器。这意味着在一段时间之后,G1 垃圾回收主要由 G1 Humongous 主导,而这通常是非常规的情况。 

在讨论 GraalVM 之前,我们需要注意的是,Java 10 为 Graal 编译器提供了另一种使用方式,即 Ahead-of-Time 编译器模式。

Graal(作为编译器)是一个从头开始开发的全新编译器,符合新的 JVM 接口(JVMCI)。所以,Graal 可以与 HotSpot 集成,但又不受其约束。

我们可以考虑使用 Graal 在离线模式下对所有方法进行全面编译而不执行代码,而不是使用配置驱动的方式编译热方法。这也就是“Ahead-of-Time 编译”(JEP 295)。

在 HotSpot 环境中,我们可以用它来生成共享对象 / 库(Linux 上的.so 或 Mac 上的.dylib),如下所示:

$ jaotc --output libStringHash.dylib kathik/StringHash.class

然后我们可以在以后的运行中使用已编译的代码:

$ java -XX:AOTLibrary=./libStringHash.dylib kathik.StringHash

这样用 Graal 只为了一个目的——加快启动速度,直到 HotSpot 的常规分层编译器可以接管编译工作。在完整的应用程序中,JIT 编译的实际测试基准应该能够胜过 AOT 编译,尽管具体情况要取决于实际的工作负载。

AOT 编译技术仍然是最前沿的,而且从技术上讲只支持(甚至是实验性质的)linux/x64。例如,在 Mac 上尝试编译 java.base 模块时,会出现以下错误(尽管仍会生成.dylib 文件):

$ jaotc --output libjava.base.dylib --module java.base
Error: Failed compilation: sun.reflect.misc.Trampoline.invoke(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: java.lang.Error: Trampoline must not be defined by the bootstrap classloader
       at parsing java.base@10/sun.reflect.misc.Trampoline.invoke(MethodUtil.java:70)
Error: Failed compilation: sun.reflect.misc.Trampoline.<clinit>()V: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: java.lang.NoClassDefFoundError: Could not initialize class sun.reflect.misc.Trampoline
       at parsing java.base@10/sun.reflect.misc.Trampoline.<clinit>(MethodUtil.java:50)

我们可以使用编译器指令文件来控制这些错误,从 AOT 编译中排除掉某些方法(有关详细信息,请参阅 JEP 295 )。

尽管存在编译器错误,我们仍然可以尝试将 AOT 编译的基本模块代码和用户代码一起运行,如下所示:

java -XX:+PrintCompilation \
     -XX:AOTLibrary=./libStringHash.dylib,libjava.base.dylib \
     kathik.StringHash

打开 PrintCompilation 标记,就可以看到 JIT 的编译情况——现在几乎没有。现在只有一些初始引导程序要用到的核心方法需要进行 JIT 编译:

   111    1     n 0       java.lang.Object::hashCode (native)  
   115    2     n 0       java.lang.Module::addExportsToAllUnnamed0 (native)   (static)

因此,我们可以得出结论,这个简单的 Java 应用程序现在是在几乎 100%的 AOT 编译模式下运行。

现在回到 GraalVM,让我们看一下该平台提供的重磅功能——能够将多种语言完整地嵌入到运行在 GraalVM 上的 Java 应用程序中。

这可以被认为是 JSR 223(Java 平台的脚本)的等效或替代方案,不过 Graal 比之前的 HotSpot 走得更深入更远。

该功能依赖于 GraalVM 和 Graal SDK——GraalVM 默认的类路径中包含了 Graal SDK,但在 IDE 中需要显式指定,例如:

<dependency>
    <groupId>org.graalvm</groupId>
    <artifactId>graal-sdk</artifactId>
    <version>1.0.0-rc1</version>
</dependency>

最简单的例子是 Hello World——让我们使用 GraalVM 默认提供的 Javascript 实现:

import org.graalvm.polyglot.Context;

public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello World: Java!");
        Context context = Context.create();
        context.eval("js", "print('Hello World: JavaScript!');");
    }
}

这在 GraalVM 上可以按预期运行,但尝试在 Java 10 上运行时,即使使用了 Graal SDK,仍然会产生这个(不足为奇的)错误:

$ java -cp target/classes:$HOME/.m2/repository/org/graalvm/graal-sdk/1.0.0-rc1/graal-sdk-1.0.0-rc1.jar kathik.HelloPolyglot
Hello Java!
Exception in thread "main" java.lang.IllegalStateException: No language and polyglot implementation was found on the classpath. Make sure the truffle-api.jar is on the classpath.
       at org.graalvm.polyglot.Engine$PolyglotInvalid.noPolyglotImplementationFound(Engine.java:548)
       at org.graalvm.polyglot.Engine$PolyglotInvalid.buildEngine(Engine.java:538)
       at org.graalvm.polyglot.Engine$Builder.build(Engine.java:367)
       at org.graalvm.polyglot.Context$Builder.build(Context.java:528)
       at org.graalvm.polyglot.Context.create(Context.java:294)
       at kathik.HelloPolyglot.main(HelloPolyglot.java:8)

自 Java 6 以来,随着 Scripting API 的引入,已经支持多语言。随着 Nashorn(基于 invokedynamic 的 JavaScript 实现)的出现,Java 8 对多语言的支持有了显著增强。

GraalVM 的与众不同之处在于,Java 生态系统现在明确提供了 SDK 和支持工具,用于实现多语言,并让它们成为运行在底层 VM 之上的平等且可互操作的公民。

完成这一步的关键在于一个叫作 Truffle 的组件和一个简单的 VM——SubstrateVM(能够执行 JVM 字节码)。

Truffle 为创建新语言实现提供了 SDK 和工具。一般过程如下:

  • 从语法开始
  • 应用解析器生成器(例如 Coco/R
  • 使用 Maven 构建解释器和简单的语言运行时
  • 在 GraalVM 上运行生成的语言实现
  • 等待 Graal(在 JIT 模式下)启动,自动增强新语言的性能
  • 在 AOT 模式下使用 Graal 将解释器编译为本机启动器(可选)

GraalVM 默认支持 JVM 字节码、JavaScript 和 LLVM。如果我们尝试向下面这样调用另一种语言,比如 Ruby:

context.eval("ruby", "puts \"Hello World: Ruby\"");

GraalVM 会抛出一个运行时异常:

Exception in thread "main" java.lang.IllegalStateException: A language with id 'ruby' is not installed. Installed languages are: [js, llvm].
       at com.oracle.truffle.api.vm.PolyglotEngineImpl.requirePublicLanguage(PolyglotEngineImpl.java:559)
       at com.oracle.truffle.api.vm.PolyglotContextImpl.requirePublicLanguage(PolyglotContextImpl.java:738)
       at com.oracle.truffle.api.vm.PolyglotContextImpl.eval(PolyglotContextImpl.java:715)
       at org.graalvm.polyglot.Context.eval(Context.java:311)
       at org.graalvm.polyglot.Context.eval(Context.java:336)
       at kathik.HelloPolyglot.main(HelloPolyglot.java:10)

要使用(当前为测试版)Truffle 版本的 Ruby(或其他语言),需要下载并安装它。对于 Graal 版本的 RC1(很快会推出 RC2),可以通过以下方式安装:

gu -v install -c org.graalvm.ruby

要注意,如果 GraalVM 是在系统级别安装的,则需要 sudo。如果使用的是 GraalVM 的非 OSS EE 版本(目前 Mac 上只有这个版本可用),则可以更进一步——可以将 Truffle 解释器转为本机代码。

为语言重建本机镜像(启动程序)可以提高它的性能,但这需要使用命令行工具,比如(假设 GraalVM 是安装在系统级别,因此需要 root 权限):

$ cd $JAVA_HOME
$ sudo jre/lib/svm/bin/rebuild-images ruby

这个工具还处于开发阶段,所以需要进行一些手动操作,开发团队希望在后续让这个流程变得更加顺畅。

如果在重建本机组件时遇到任何问题,请不要担心——即使不重建本机镜像仍然可以正常使用它。

让我们看一个更复杂的多语言示例:

Context context = Context.newBuilder().allowAllAccess(true).build();
Value sayHello = context.eval("ruby",
        "class HelloWorld\n" +
        "   def hello(name)\n" +
        "      \"Hello #{name}\"\n" +
        "   end\n" +
        "end\n" +
        "hi = HelloWorld.new\n" +
        "hi.hello(\"Ruby\")\n");
String rubySays = sayHello.as(String.class);
Value jsFunc = context.eval("js",
        "function(x) print('Hello World: JavaScript with '+ x +'!');");
jsFunc.execute(rubySays);

这段代码有点难以阅读,它同时用到了 TruffleRuby 和 JavaScript。首先,我们调用了一段 Ruby 代码:

class HelloWorld
   def hello(name)
      "Hello #{name}"
   end
end

hi = HelloWorld.new
hi.hello("Ruby")

这将创建一个新的 Ruby 类,并为这个类定义了一个方法,然后实例化了一个 Ruby 对象,最后调用它的 hello() 方法。这个方法返回一个(Ruby)字符串,该字符串在 Java 运行时中被强制转换为 Java 字符串。

然后我们创建了一个简单的 JavaScript 匿名函数,如下所示:

function(x) print('Hello World: JavaScript with '+ x +'!');

我们通过 execute() 调用这个函数,并将 Ruby 返回的结果传给函数,该函数在 JS 运行时中将其打印出来。

请注意,我们在创建 Context 对象时,需要放开该对象的访问权限。这样做是为了 Ruby——JS 没有这个问题——所以在创建对象时稍微复杂了一些。这是由当前的 Ruby 实现限制造成的,这个限制将来可能会被移除。

让我们看一个最终的多语言示例:

Value sayHello = context.eval("ruby",
        "class HelloWorld\n" +
        "   def hello(name)\n" +
        "      \"Hello Ruby: #{name}\"\n" +
        "   end\n" +
        "end\n" +
        "hi = HelloWorld.new\n" +
        "hi");
Value jsFunc = context.eval("js",
        "function(x) print('Hello World: JS with '+ x.hello('Cross-call') +'!');");
jsFunc.execute(sayHello);

在这个版本中,我们返回一个实际的 Ruby 对象,而不仅仅是一个字符串。这次我们没有将它强制转换为任何 Java 类型,而是将其直接传给这个 JS 函数:

function(x) print('Hello World: JS with '+ x.hello('Cross-call') +'!');

它输出了预期的内容:

Hello World: Java!
Hello World: JS with Hello Ruby: Cross-call!

这说明 JS 运行时可以调用处于其他运行时中的对象的方法,并进行无缝类型转换(至少可以进行简单类型转换)。

对于这种可跨多种具有不同语义和类型系统的语言的可互换能力,JVM 工程师已经讨论了很长一段时间(至少 10 年),而随着 GraalVM 的到来,它向主流迈出了非常重要的一步。

让我们使用这一小段打印 Ruby 对象的 JS 代码演示这些外部对象是如何在 GraalVM 中表示的:

function(x) print('Hello World: JS with '+ x +'!');

输出如下(或类似这样的):

Hello World: JS with foreign {is_a?: DynamicObject@540a903b<Method>, extend: DynamicObject@238acd0b<Method>, protected_methods: DynamicObject@34e20e6b<Method>, public_methods: DynamicObject@15ac59c2<Method>, ...}!

这些输出显示了外部对象被表示为一系列 DynamicObject 对象,在大多数情况下,它将语义操作委托给对象的主运行时。

在结束本文之前,我们应该谈谈基准和许可。我们必须搞清楚的是,尽管 Graal 和 GraalVM 有着巨大的前景,但目前仍处于早期阶段 / 实验技术阶段。

它尚未针对通用场景进行优化,并且尚需时日才能与 HotSpot/C2 平起平坐。微基准通常也会产生误导——在某些情况下它们可以指明方向,但对于性能分析来说,只有最终的用户级基准才算数。

我们可以这样想,C2 已经最大限度地提升了局部性能,并且即将寿终正寝。Graal 让我们有机会突破局部最大化,并转到一个更好的新领域——并且有可能会重新构思我们对 VM 设计和编译器的许多想法。但它仍然不够成熟,并且不太可能在几年内完全成为主流。

这意味着现在进行的任何性能测试都应该进行谨慎分析。性能测试的比较(特别是 HotSpot/C2 与 GraalVM)是苹果与橙子之间的比较——一个成熟的生产级运行时与一个还处于早期阶段的实验性产品。

还需要指出的是,GraalVM 的许可制度可能与迄今为止看到的有所不同。甲骨文在收购 Sun 公司时,HotSpot 已经是非常成熟的产品,并被冠以自由软件许可。他们很少在 HotSpot 核心产品之上增加价值和进行变现——例如 UnlockCommercialFeatures 开关。随着这些功能的退出(比如开源Mission Control ),可以说,该模型并没有取得巨大的商业成功。

Graal 与众不同——它起源于甲骨文 Research 项目,现在正朝着生产产品的方向发展。甲骨文已投入大量资金让 Graal 成为现实——该项目所需的人才和团队不足,而且他们都不便宜。因为使用了不同的底层技术,甲骨文可以自由地使用不同的商业许可模型,并尝试基于更广泛的客户群为 GraalVM 变现——包括那些目前不为 HotSpot 运行付费的客户。甲骨文甚至可以将 GraalVM 的某些功能定向提供给甲骨文云客户使用。

目前,甲骨文正在发布一个基于 GPL 许可的社区版本(CE),它可以免费用于开发和生产用途,以及一个企业版(EE),它可以免费用于开发和评估。这两个版本都可以从甲骨文的 GraalVM 网站下载,其中还可以找到更详细的信息。

关于作者

Ben Evans 是 JVM 性能优化公司 jClarity 的联合创始人。他是 LJC(伦敦 JUG)的组织者,也是 JCP 执行委员会的成员,帮助定义 Java 生态系统的标准。Ben 是 Java Champion、3 次 JavaOne Rockstar 演讲者,“The Well-Grounded Java Developer”、新版“Java in a Nutshell”和“Optimizing Java”的作者。他是 Java 平台、性能、架构、并发、初创公司和相关主题的演讲常客。Ben 有时也接受演讲、教学、写作和咨询活动的邀请,具体可以联系他。

查看英文原文 Getting to Know Graal, the New Java JIT Compiler

公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2018-07-26 18:159569
用户头像

发布了 731 篇内容, 共 433.6 次阅读, 收获喜欢 1997 次。

关注

评论 1 条评论

发布
暂无评论
发现更多内容

Vue进阶(幺叁玖):textarea文本框根据内容自适应改变高度

No Silver Bullet

Vue 10月月更

filecoin云算力挖矿系统软件开发源码搭建

现成filecoin算力挖矿软件系统开发公司

线程池处理批量接口请求实践

FunTester

线程池 性能测试 接口测试 测试框架 FunTester

康威定律

俞凡

架构 认知

应对多样管理需求,新版 EMQ X Cloud 实现多项目部署管理

EMQ映云科技

物联网 mqtt

技术人在职场应该知道的沟通技巧

baiyutang

沟通模型 10月月更

这篇博客和你唠唠 python 并发,滚雪球学python第四季,第16篇

梦想橡皮擦

10月月更

北鲲云超算如何让仿真技术、HPC和人工智能之间的深度融合?

北鲲云

filecoin矿机分币系统开发方案(现成)

竞跑加速! 数字人民币场景全覆盖

CECBC

网站性能优化的实战指南

devpoint

html 性能优化 10月月更

linux之sed使用技巧

入门小站

Linux

Golang网络编程

CodeWithBuff

golang 网络编程 Go 语言 web socket

Apache APISIX 2.10.0 正式发布,带来第一个 LTS 版本!

API7.ai 技术团队

开源 API网关 APISIX Apache APISIX

Apache APISIX 社区周报 | 2021 10.1-10.14

API7.ai 技术团队

开源社区 API网关 APISIX 社区周报 Apache APISIX

【Flutter 专题】27 图解 ListView/GridView 混用时滑动冲突小尝试

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

官方线索|第二届1024国际智能投研开发者大会

穿过生命散发芬芳

1024我在现场

架构:微内核架构(Microkernel Architecture)二

程序员架构进阶

架构 规则引擎 微内核架构 OSGi 10月月更

官方线索|CCF CED 中国工程师文化日

穿过生命散发芬芳

1024我在现场

[论文分享]VulDeePecker:基于深度学习的漏洞检测技术

maijun

机器学习 静态代码分析 VulDeePecker 漏洞检测

【日志技术专题】「logback入门到精通」彻彻底底带你学会logback框架的使用和原理(入门介绍篇)

洛神灬殇

Java logback 日志系统 10月月更

Java容器学习二

风翱

Java 10月月更

官方线索|RTE2021 实时互联网大会

穿过生命散发芬芳

1024我在现场

官方线索|Gitee2021程序员节特别活动

穿过生命散发芬芳

1024我在现场

Java常用容器笔记

风翱

java 10月月更

在线中文繁简体转换工具

入门小站

工具

数字货币已被世界公认,中国市场即将爆发

CECBC

和12岁小同志搞创客开发:如何驱动红外遥控器?

不脱发的程序猿

少儿编程 DIY 红外遥控器 创客开发

Android开发中遇到加载有相同函数的so库时的问题

轻口味

10月月更

基于分布式认知工业互联网的汽车零部件质量溯源平台

CECBC

解密新一代Java JIT编译器Graal_Java_Ben Evans_InfoQ精选文章