为什么此代码在 Go 中与 Java 中运行需要更长的时间
Go 语言中的循环比 Java 语言中慢的原因尚未明确。一些人认为这是由于 Go 编译器生成的代码效率较低,而另一些人则认为 Java 的 JIT 优化器对循环进行了重大优化。为了探索这一差异,本文比较了 Go 和 Java 中一个简单循环的性能,该循环对 200 亿个数字进行计数。结果显示,Go 版本的循环比 Java 版本慢得多,平均运行时间为 5,851 毫秒,而 Java 版本的平均运行时间为 392 毫秒。本文分析了 Go 编译器生成的代码,发现它非常复杂且冗长,而 Java 字节码相对简单。本文还讨论了 JIT 优化可能对 Java 代码产生的影响,并指出这种优化可能预先计算循环变量的值,从而显著提高性能。
最近开始学习 go,我是一个忠实的粉丝,有 java 背景。
我以不同的方式比较了这些语言,令我惊讶的是,与 java 相比,计数到 200 亿的简单循环在 golang 中花费的时间要长得多。
想知道是否有人可以对我在这里遗漏的内容提供任何见解。这就是我所做的:
java
编写以下代码,从普通的 main() 方法执行它,使用 gradle 构建可执行 jar,并使用以下命令从命令行执行它: java -jar build/libs/my-executable.jar
private void counttotwentybillion() {
long count = 0;
long start = system.currenttimemillis();
for (int k = 0; k < 10; k++) {
system.out.println("on step " + k);
for (int i = 0; i < 2_000_000_000; i++) {
// do nothing but count
count++;
}
}
long end = system.currenttimemillis();
system.out.println("total time took: " + (end - start) + " ms to get at count: " + count);
}
通过 3 个单独的试验,我得到了以下结果:
// total time took: 396 ms to get at count: 20000000000 // total time took: 393 ms to get at count: 20000000000 // total time took: 388 ms to get at count: 20000000000 // 392 ms average
去
在 go 中构建此文件,使用“go build”构建并使用 在命令行中执行。/loop-counter
package main
import (
"fmt"
"time"
)
func main() {
count := 0
nanos := time.now().unixnano()
start := nanos / 1000000
for i := 0; i < 10; i++ {
fmt.printf("on step %d\n", i)
for k := 0; k < 2000000000; k++ {
count++
}
}
nanos = time.now().unixnano()
end := nanos / 1000000
timelength := end - start
fmt.printf("total time took: %d ms to get at count: %d\n", timelength, count)
}
经过 3 次单独的试验,我得到了以下结果:
// Total time took: 5812 ms to get at count: 20000000000 // Total time took: 5834 ms to get at count: 20000000000 // Total time took: 5907 ms to get at count: 20000000000 // 5,851 ms average
我开始期待 go 会更快,但最终却感到惊讶。所有试验均在同一台机器上、相同条件下进行。
谁能告诉我什么?
谢谢
解决方案
我不是 go 专家,但 java 确实优化了循环。
假设您有一个带有 3ghz 的单核处理器,每条指令的时间为 0.3ns,我们假设每个增量都是一条指令。因此 0.3ns *200 亿 = 6s 是在没有任何优化的情况下粗略估计的性能。
您可以通过向您的程序提供 -xx:loopunrolllimit=1 来验证 java 是否在此处进行了一些欺骗。这告诉 jvm 几乎不进行循环展开,因此可以防止大多数 jit 优化在您的示例中发生。
这样做后,您的 java 示例的运行时现在在我的机器上为 6s,这与 go 基准测试相当。
go 版本中可能还有一个选项可以启用循环展开等优化(请参阅 go 手册)。
最后,这再次表明,微基准测试很难正确执行。他们经常自欺欺人地假设一些不正确的事情。
以下是我的一些观察结果。我将展示一些通过编译该程序获得的英特尔语法汇编代码。我使用的是Compiler Explorer。要理解下面的内容,您不必了解很多汇编,这里最重要的元素是大小,它越大,速度越慢。如果可以的话我会把这篇文章缩小,但是生成的代码出奇的庞大,而且我对 go 的了解还不够,不知道什么是无用的。如果您想查看汇编中每个语句转换成的内容,编译器资源管理器将为您突出显示所有内容。
tl;dr:
在我看来,go 编译器是一个灾难性的混乱,c++ 代码得到了很好的优化,而 java 与 go 相比很小。 jit'ing 可能对 java 代码产生重大影响,对于分解内联优化的循环来说也可能太复杂(预先计算 count 的值)。
go 代码编译成这个怪物:
text "".main(sb), $224-0
movq (tls), cx
leaq -96(sp), ax
cmpq ax, 16(cx)
jls 835
subq $224, sp
movq bp, 216(sp)
leaq 216(sp), bp
funcdata $0, gclocals·f6bd6b3389b872033d462029172c8612(sb)
funcdata $1, gclocals·17283ea8379a997487dd6f8baf7ae6ea(sb)
pcdata $0, $0
call time.now(sb)
movq 16(sp), ax
movq 8(sp), cx
movq (sp), dx
movq dx, time.t·2+160(sp)
movq cx, time.t·2+168(sp)
movq ax, time.t·2+176(sp)
movq time.t·2+160(sp), ax
movq ax, cx
shrq $63, ax
shlq $63, ax
testq $-1, ax
jeq 806
movq cx, dx
shlq $1, cx
shrq $31, cx
movq $59453308800, bx
addq bx, cx
andq $1073741823, dx
movlqsx dx, dx
imulq $1000000000, cx
addq dx, cx
movq $-6795364578871345152, dx
addq dx, cx
movq $4835703278458516699, ax
imulq cx
sarq $63, cx
sarq $18, dx
subq cx, dx
movq dx, "".start+72(sp)
xorl ax, ax
movq ax, cx
jmp 257
incq cx
incq ax
cmpq cx, $2000000000
jlt 213
movq "".i+80(sp), si
incq si
movq "".start+72(sp), dx
movq $59453308800, bx
movq ax, cx
movq si, ax
movq cx, "".count+88(sp)
cmpq ax, $10
jge 404
movq ax, "".i+80(sp)
movq ax, ""..autotmp_24+112(sp)
xorps x0, x0
movups x0, ""..autotmp_23+120(sp)
leaq type.int(sb), cx
movq cx, (sp)
leaq ""..autotmp_24+112(sp), dx
movq dx, 8(sp)
pcdata $0, $1
call runtime.convt2e64(sb)
movq 24(sp), ax
movq 16(sp), cx
movq cx, ""..autotmp_23+120(sp)
movq ax, ""..autotmp_23+128(sp)
leaq go.string."on step %d\n"(sb), ax
movq ax, (sp)
movq $11, 8(sp)
leaq ""..autotmp_23+120(sp), cx
movq cx, 16(sp)
movq $1, 24(sp)
movq $1, 32(sp)
pcdata $0, $1
call fmt.printf(sb)
movq "".count+88(sp), ax
xorl cx, cx
jmp 219
pcdata $0, $2
call time.now(sb)
movq 16(sp), ax
movq 8(sp), cx
movq (sp), dx
movq dx, time.t·2+136(sp)
movq cx, time.t·2+144(sp)
movq ax, time.t·2+152(sp)
movq time.t·2+136(sp), ax
movq ax, cx
shrq $63, ax
shlq $63, ax
testq $-1, ax
jeq 787
movq cx, dx
shlq $1, cx
shrq $31, cx
movq $59453308800, bx
addq bx, cx
imulq $1000000000, cx
andq $1073741823, dx
movlqsx dx, dx
addq dx, cx
movq $-6795364578871345152, dx
leaq (dx)(cx*1), ax
movq ax, "".~r0+64(sp)
movq $4835703278458516699, cx
imulq cx
sarq $18, dx
movq "".~r0+64(sp), cx
sarq $63, cx
subq cx, dx
movq "".start+72(sp), cx
subq cx, dx
movq dx, ""..autotmp_29+104(sp)
movq "".count+88(sp), cx
movq cx, ""..autotmp_30+96(sp)
xorps x0, x0
movups x0, ""..autotmp_28+184(sp)
movups x0, ""..autotmp_28+200(sp)
leaq type.int64(sb), cx
movq cx, (sp)
leaq ""..autotmp_29+104(sp), cx
movq cx, 8(sp)
pcdata $0, $3
call runtime.convt2e64(sb)
movq 16(sp), cx
movq 24(sp), dx
movq cx, ""..autotmp_28+184(sp)
movq dx, ""..autotmp_28+192(sp)
leaq type.int(sb), cx
movq cx, (sp)
leaq ""..autotmp_30+96(sp), cx
movq cx, 8(sp)
pcdata $0, $3
call runtime.convt2e64(sb)
movq 24(sp), cx
movq 16(sp), dx
movq dx, ""..autotmp_28+200(sp)
movq cx, ""..autotmp_28+208(sp)
leaq go.string."total time took: %d to get at count: %d\n"(sb), cx
movq cx, (sp)
movq $40, 8(sp)
leaq ""..autotmp_28+184(sp), cx
movq cx, 16(sp)
movq $2, 24(sp)
movq $2, 32(sp)
pcdata $0, $3
call fmt.printf(sb)
movq 216(sp), bp
addq $224, sp
ret
movq time.t·2+144(sp), bx
movq cx, dx
movq bx, cx
jmp 501
movq time.t·2+168(sp), si
movq cx, dx
movq $59453308800, bx
movq si, cx
jmp 144
nop
pcdata $0, $-1
call runtime.morestack_noctxt(sb)
jmp 0
text "".init(sb), $8-0
movq (tls), cx
cmpq sp, 16(cx)
jls 89
subq $8, sp
movq bp, (sp)
leaq (sp), bp
funcdata $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(sb)
funcdata $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(sb)
movblzx "".initdone·(sb), ax
cmpb al, $1
jls 47
movq (sp), bp
addq $8, sp
ret
jne 56
pcdata $0, $0
call runtime.throwinit(sb)
undef
movb $1, "".initdone·(sb)
pcdata $0, $0
call fmt.init(sb)
pcdata $0, $0
call time.init(sb)
movb $2, "".initdone·(sb)
movq (sp), bp
addq $8, sp
ret
nop
pcdata $0, $-1
call runtime.morestack_noctxt(sb)
jmp 0
text type..hash.[2]interface {}(sb), dupok, $40-24
movq (tls), cx
cmpq sp, 16(cx)
jls 103
subq $40, sp
movq bp, 32(sp)
leaq 32(sp), bp
funcdata $0, gclocals·d4dc2f11db048877dbc0f60a22b4adb3(sb)
funcdata $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(sb)
xorl ax, ax
movq "".h+56(sp), cx
jmp 82
movq ax, "".i+24(sp)
shlq $4, ax
movq "".p+48(sp), bx
addq bx, ax
movq ax, (sp)
movq cx, 8(sp)
pcdata $0, $0
call runtime.nilinterhash(sb)
movq 16(sp), cx
movq "".i+24(sp), ax
incq ax
cmpq ax, $2
jlt 38
movq cx, "".~r2+64(sp)
movq 32(sp), bp
addq $40, sp
ret
nop
pcdata $0, $-1
call runtime.morestack_noctxt(sb)
jmp 0
text type..eq.[2]interface {}(sb), dupok, $48-24
movq (tls), cx
cmpq sp, 16(cx)
jls 155
subq $48, sp
movq bp, 40(sp)
leaq 40(sp), bp
funcdata $0, gclocals·8f9cec06d1ae35cc9900c511c5e4bdab(sb)
funcdata $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(sb)
xorl ax, ax
jmp 46
movq ""..autotmp_8+32(sp), cx
leaq 1(cx), ax
cmpq ax, $2
jge 140
movq ax, cx
shlq $4, ax
movq "".p+56(sp), dx
movq 8(ax)(dx*1), bx
movq (ax)(dx*1), si
movq "".q+64(sp), di
movq 8(ax)(di*1), r8
movq (ax)(di*1), ax
cmpq si, ax
jne 125
movq cx, ""..autotmp_8+32(sp)
movq si, (sp)
movq bx, 8(sp)
movq r8, 16(sp)
pcdata $0, $0
call runtime.efaceeq(sb)
movblzx 24(sp), ax
testb al, al
jne 37
movb $0, "".~r2+72(sp)
movq 40(sp), bp
addq $48, sp
ret
movb $1, "".~r2+72(sp)
movq 40(sp), bp
addq $48, sp
ret
nop
pcdata $0, $-1
call runtime.morestack_noctxt(sb)
jmp 0
我不知道其中大部分在做什么。我只能希望其中大部分是某种 gc 代码。我查找了如何启用 go 编译器的优化,我所能找到的只是如何禁用优化。
相比之下,我在 c++ 中查看了类似的函数
#include <cstdio>
#include <chrono>
#include <cinttypes>
using namespace std::chrono;
milliseconds getms()
{
return duration_cast< milliseconds >(
system_clock::now().time_since_epoch()
);
}
int main()
{
int count = 0;
milliseconds millis = getms();
for(int i = 0; i < 10; ++i)
{
printf("on step %d\n", i);
for(int j = 0; j < 2000000000; ++j)
{
++count;
}
}
milliseconds time = getms() - millis;
printf("total time took: %" prid64 " to get at count: %d\n", time.count(), count);
}
未经优化编译为(编译器x86-64 clang(trunk(可能是6.0.0),标志:-std=c++0x -o0):
main: # @main
push rbp
mov rbp, rsp
sub rsp, 48
mov dword ptr [rbp - 4], 0
mov dword ptr [rbp - 8], 0
call getms()
mov qword ptr [rbp - 16], rax
mov dword ptr [rbp - 20], 0
.lbb3_1: # =>this loop header: depth=1
cmp dword ptr [rbp - 20], 10
jge .lbb3_8
mov esi, dword ptr [rbp - 20]
movabs rdi, offset .l.str
mov al, 0
call printf
mov dword ptr [rbp - 24], 0
mov dword ptr [rbp - 44], eax # 4-byte spill
.lbb3_3: # parent loop bb3_1 depth=1
cmp dword ptr [rbp - 24], 2000000000
jge .lbb3_6
mov eax, dword ptr [rbp - 8]
add eax, 1
mov dword ptr [rbp - 8], eax
mov eax, dword ptr [rbp - 24]
add eax, 1
mov dword ptr [rbp - 24], eax
jmp .lbb3_3
.lbb3_6: # in loop: header=bb3_1 depth=1
jmp .lbb3_7
.lbb3_7: # in loop: header=bb3_1 depth=1
mov eax, dword ptr [rbp - 20]
add eax, 1
mov dword ptr [rbp - 20], eax
jmp .lbb3_1
.lbb3_8:
call getms()
mov qword ptr [rbp - 40], rax
lea rdi, [rbp - 40]
lea rsi, [rbp - 16]
call std::common_type<std::chrono::duration<long, std::ratio<1l, 1000l> >, std::chrono::duration<long, std::ratio<1l, 1000l> > >::type std::chrono::operator-<long, std::ratio<1l, 1000l>, long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)
mov qword ptr [rbp - 32], rax
lea rdi, [rbp - 32]
call std::chrono::duration<long, std::ratio<1l, 1000l> >::count() const
mov edx, dword ptr [rbp - 8]
movabs rdi, offset .l.str.1
mov rsi, rax
mov al, 0
call printf
mov edx, dword ptr [rbp - 4]
mov dword ptr [rbp - 48], eax # 4-byte spill
mov eax, edx
add rsp, 48
pop rbp
ret
.l.str:
.asciz "on step %d\n"
.l.str.1:
.asciz "total time took: %ld to get at count: %d\n"
实际上还有很多代码,但它只是 chrono 实现,在优化后的代码中它只是一个库函数调用。我还删除了 getms 的实现,因为它主要是一个包装方法。
通过 o1(大小)优化,这会变成:
main: # @main
push rbx
sub rsp, 32
call getms()
mov qword ptr [rsp + 24], rax
xor ebx, ebx
.lbb3_1: # =>this inner loop header: depth=1
mov edi, offset .l.str
xor eax, eax
mov esi, ebx
call printf
add ebx, 1
cmp ebx, 10
jne .lbb3_1
call getms()
mov qword ptr [rsp + 8], rax
lea rdi, [rsp + 8]
lea rsi, [rsp + 24]
call std::common_type<std::chrono::duration<long, std::ratio<1l, 1000l> >, std::chrono::duration<long, std::ratio<1l, 1000l> > >::type std::chrono::operator-<long, std::ratio<1l, 1000l>, long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)
mov qword ptr [rsp + 16], rax
lea rdi, [rsp + 16]
call std::chrono::duration<long, std::ratio<1l, 1000l> >::count() const
mov rcx, rax
mov edi, offset .l.str.1
mov edx, -1474836480
xor eax, eax
mov rsi, rcx
call printf
xor eax, eax
add rsp, 32
pop rbx
ret
.l.str:
.asciz "on step %d\n"
.l.str.1:
.asciz "total time took: %ld to get at count: %d\n"
o2(速度)和 o3(最大)优化本质上归结为展开的外循环(仅用于打印语句)和预先计算的计数值。
这主要显示了 go 生成的糟糕代码以及 c++ 中发生的一些优化。但这些都没有准确显示 java 字节码包含什么内容,或者如果运行足够多的时间,jit 会生成什么内容。这是 java 字节码:
public static void countToTwentyBillion();
Code:
0: lconst_0
1: lstore_0
2: invokestatic #2
// Method java/lang/System.currentTimeMillis:()J
5: lstore_2
6: iconst_0
7: istore
4
9: iload
4
11: bipush
10
13: if_icmpge
68
16: getstatic
#3
// Field java/lang/System.out:Ljava/io/PrintStream;
19: new
#4
// class java/lang/StringBuilder
22: dup
23: invokespecial #5
// Method java/lang/StringBuilder.'<init>':()V
26: ldc
#6
// String On step
28: invokevirtual #7
// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: iload
4
33: invokevirtual #8
// Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
36: invokevirtual #9
// Method java/lang/StringBuilder.toString:()Ljava/lang/String;
39: invokevirtual #10
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: iconst_0
43: istore
5
45: iload
5
47: ldc
#11
// int 2000000000
49: if_icmpge
62
52: lload_0
53: lconst_1
54: ladd
55: lstore_0
56: iinc
5, 1
59: goto
45
62: iinc
4, 1
65: goto
9
68: invokestatic #2
// Method java/lang/System.currentTimeMillis:()J
71: lstore
4
73: getstatic
#3
// Field java/lang/System.out:Ljava/io/PrintStream;
76: new
#4
// class java/lang/StringBuilder
79: dup
80: invokespecial #5
// Method java/lang/StringBuilder.'<init>':()V
83: ldc
#12
// String Total time took:
85: invokevirtual #7
// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
88: lload
4
90: lload_2
91: lsub
92: invokevirtual #13
// Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
95: ldc
#14
// String ms to get at count:
97: invokevirtual #7
// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
100: lload_0
101: invokevirtual #13
// Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
104: invokevirtual #9
// Method java/lang/StringBuilder.toString:()Ljava/lang/String;
107: invokevirtual #10
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
110: return
不幸的是,目前我不想编译 hsdis 和 jit 代码,但它可能最终看起来像一些 c++ 示例。根据我对 jit 的了解,它可能能够预先计算计数值。但这段代码有点复杂(就循环而言),这可能会使快速 jit 优化变得更加困难。
以上就是《为什么此代码在 Go 中与 Java 中运行需要更长的时间》的详细内容,更多关于的资料请关注golang学习网公众号!
在迭代映射的同时并发修改映射时,如何使用 RWMutex
- 上一篇
- 在迭代映射的同时并发修改映射时,如何使用 RWMutex
- 下一篇
- Go http.FileServer 不提供所有静态内容
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 478浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3204次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3416次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3446次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4555次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3824次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览

