当前位置:首页 > 文章列表 > Golang > Go问答 > 为什么此代码在 Go 中与 Java 中运行需要更长的时间

为什么此代码在 Go 中与 Java 中运行需要更长的时间

来源:stackoverflow 2024-03-16 12:21:32 0浏览 收藏

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 
#include 
#include 

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 > >::type std::chrono::operator-, long, std::ratio<1l, 1000l> >(std::chrono::duration > const&, std::chrono::duration > const&)
        mov     qword ptr [rbp - 32], rax
        lea     rdi, [rbp - 32]
        call    std::chrono::duration >::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 > >::type std::chrono::operator-, long, std::ratio<1l, 1000l> >(std::chrono::duration > const&, std::chrono::duration > const&)
        mov     qword ptr [rsp + 16], rax
        lea     rdi, [rsp + 16]
        call    std::chrono::duration >::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.'':()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.'':()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学习网公众号!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
在迭代映射的同时并发修改映射时,如何使用 RWMutex在迭代映射的同时并发修改映射时,如何使用 RWMutex
上一篇
在迭代映射的同时并发修改映射时,如何使用 RWMutex
Go http.FileServer 不提供所有静态内容
下一篇
Go http.FileServer 不提供所有静态内容
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • AI Make Song:零门槛AI音乐创作平台,助你轻松制作个性化音乐
    AI Make Song
    AI Make Song是一款革命性的AI音乐生成平台,提供文本和歌词转音乐的双模式输入,支持多语言及商业友好版权体系。无论你是音乐爱好者、内容创作者还是广告从业者,都能在这里实现“用文字创造音乐”的梦想。平台已生成超百万首原创音乐,覆盖全球20个国家,用户满意度高达95%。
    26次使用
  • SongGenerator.io:零门槛AI音乐生成器,快速创作高质量音乐
    SongGenerator
    探索SongGenerator.io,零门槛、全免费的AI音乐生成器。无需注册,通过简单文本输入即可生成多风格音乐,适用于内容创作者、音乐爱好者和教育工作者。日均生成量超10万次,全球50国家用户信赖。
    21次使用
  •  BeArt AI换脸:免费在线工具,轻松实现照片、视频、GIF换脸
    BeArt AI换脸
    探索BeArt AI换脸工具,免费在线使用,无需下载软件,即可对照片、视频和GIF进行高质量换脸。体验快速、流畅、无水印的换脸效果,适用于娱乐创作、影视制作、广告营销等多种场景。
    23次使用
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    23次使用
  • Brev AI:零注册门槛的全功能免费AI音乐创作平台
    Brev AI
    探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
    25次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码