【问题标题】:Is there a C-like mini-syntax/language that can be both translated to native C/C++ and Java?是否有一种类似 C 的迷你语法/语言可以翻译成原生 C/C++ 和 Java?
【发布时间】:2019-03-10 22:11:45
【问题描述】:

我想让我的应用程序被“脚本化”。脚本语言应该是类型化的和类似 C 的。它应该具有在 C 和 Java 中都可以找到的常用控制语句、原始类型、数组和运算符。脚本应该能够定义函数,并调用预定义的 API 函数,例如我选择提供的 sin() ...。如果脚本通过了语法检查,那么它将被应用程序翻译成 Java,然后即时编译,但也应该可以将其翻译成 C/C++,并进行本地编译。 Java/C 的语法检查和翻译应该在 JVM 中运行。我不需要解释器,因为它总是会被翻译成 Java/C。只是一个语法检查器和翻译器。

那里有这样的语言吗?如果不是,考虑到我不熟悉编译器/解释器编程,在 JVM 中执行此操作的最简单方法是什么? (如果我是,我就不需要问这个问题了......)

如果有“Scala”解决方案,那也很好,因为我实际上是在将我的 Java 代码迁移到 Scala。

[编辑] 我想要 C/C++ 翻译的唯一原因是性能。我期望对数组进行很多“位操作”,而 Java 并不真正适合,特别是由于在每个数组索引操作时都会进行范围检查。我还期望有很多对象,这会在 GC 周期中间接消耗。

[EDIT#2] 好的,我明白我必须得到具体的。我正在考虑对 Minecraft 克隆进行编程,作为练习,让我的注意力从“商业计算”中解脱出来。我说的是引擎,而不是游戏玩法。而且我对服务器端比对 3D 更感兴趣,因为我是一个“服务器人”。我们正在谈论使用飞行重量模式来表示数百万个对象(块/体素),并每秒多次访问它们。这不是 JVM 的用途。请看这篇文章以了解我的意思:Why we chose CPP over Java

我不想用 C/C++ 编写所有程序,但我怀疑这是获得良好性能的唯一方法。我的意思的另一个例子是VoltDB,它可以说是目前最快的 SQL 数据库。他们用 Java 和 C/C++ 编写它,使用 Java 进行 I/O 和网络,使用 C 进行繁重的内存操作和有点摸索。用户编写的存储过程是用 Java 编写的,但我认为不需要。我认为应该可以在客户端和测试构建中编译为 Java,并在服务器上编译为 C,可以配置完整的开发环境。

【问题讨论】:

  • 我猜答案是否定的。为什么?因为如果你需要做任何有趣的事情,那么你就需要开始分配和释放内存,而 C 和 Java 的分歧如此之大,以至于整个想法变得有点无用。如果您需要一门脚本语言,为什么不跳出更高的级别并选择 Python 或 Ruby?它们在“C”世界和“Java”世界都有实现......
  • cdegroot,它可能不漂亮,但理论证明你可以将任何两个图灵完备的编程模型编译成彼此。换句话说:是的,你可以,但你真的、真的想要吗?
  • 我第二个@cdegroot,集成真正的脚本语言可能是更好的解决方案。 Ruby 和 Python 已命名,但我也推荐 Lua,因为它非常小。
  • 当您极其严格地限制可能的操作时,我认为可能存在同时是有效的 C 和 Java 代码的代码片段。例如int i = sin(x); 可以是 C 或 Java。因此,如果您将自己限制在这样的表达式中,您可能会想出一组不同的页眉和页脚,用它们包围代码,以使其成为有效的 Java 类或 C 程序。但是……这对我来说听起来不是好的设计。
  • 您能否详细说明您实际需要的编程语言结构?也许是一个小而现实的代码示例。你需要条件、循环、函数、嵌套函数、类、虚拟方法、运算符重载、模块吗?当您的需求足够小时,创建一个可以将自身转换为 Java 或 C 的 Scala DSL 实际上可能是有意义的。

标签: java c scala code-translation


【解决方案1】:

也许Haxe 会满足您的需求。它是一种中级高级语言,可以编译成 C++ 源代码。 Java 目标位于development

【讨论】:

  • Haxe/Java 上的最后一个活动似乎是在“2010 年 3 月 16 日”,所以 Haxe/Java 基本上已经死了。
【解决方案2】:

两个字:过早优化。

您担心性能。但是考虑到你想制作一个 Minecraft 克隆,这意味着游戏世界可以很好地用一个 3D 数组来表示。在所有提到的编程语言中访问这些都相当快;游戏逻辑的执行时间应该比访问数百万个数组条目要多得多。那么,为什么要优化一个无论如何都不会占用大部分计算时间的部分——甚至在你编写一个最低限度的工作版本之前呢?

您可能想要创建代表游戏世界的 Java 接口或 Scala 特征。它提供了获取和存储游戏世界块内容的方法。稍后,您还可以添加批量方法以进一步优化性能;例如,将检查给定立方体中的所有块是否都是空的,或者计算木块的数量,沿着这些线。但一开始,最好忽略那些方法,或者做一些依赖重复调用抽象方法的琐碎实现。您可以稍后对其进行优化。

然后您可以提供该接口的一个非常简单的 Java/Scala 实现,它实际上使用了一个 3 维数组。另一种方法是映射其键是坐标,值是块状态。优点是游戏世界的大小没有真正的限制,空块不会占用任何内存(对于空块的坐标,地图中没有条目)。缺点显然是性能。

此时,如果数据占用过多内存,您可能需要考虑更紧密地打包数据。您可以使用位集。当您到达那个阶段时,使用 JNI 将一些用 C 或 C++ 编写的代码注入 JVM 中实际上是有意义的。因此,您将游戏逻辑保留在 Java/Scala 中,并在 C 中进行内存打包和查找。

创建一个可以创建 Java/Scala 和 C/C++ 版本的代码本机部分的通用“脚本”源没有真正意义;本机 C/C++ 函数将严重依赖无法直接转换为 Java 的优化。当您想以“纯 Java/Scala 模式”启动服务器时,即没有 JNI 函数,只需使用您在上一步中创建的其他类。它们可能会慢一点,但它们是纯 JVM 字节码。而且由于您使它们保持简单,因此您不必疯狂地扩展它们或向它们引入新的错误。至少,创建或调整跨编程语言代码生成器的开销远远大于保留两个单独的代码库,尤其是在 Java/Scala 实现非常简单的情况下。

当然,位包装只能让你走到这一步。您可能想注意到游戏世界的某些部分几乎完全是空的(尤其是地表以上的部分),而其他部分则包含充满相同类型块的巨大区域(例如几乎完全由石头组成的地下区域)。维护一个具有这么多冗余的巨大内存结构确实是在浪费内存。因此,您可能会考虑将游戏世界打包成一棵树,其中每个节点代表游戏世界的一个大立方体区域,孩子们将其进一步细分,直至描述仅描述一个特定游戏世界坐标内容的叶子。当一个节点只有相同内容类型的子节点时,您不需要存储子节点。在这一点上简单地砍树,让节点说,“你不需要再看远了。我是满水的,所以我里面的每个坐标都指向一个水瓦。” - 这将大大减少内存使用量。只有游戏世界中真正复杂的部分才会消耗大量内存,这是正确的。世界上比较无聊的部分只占据树中的几个节点。这很好。由于它是一棵树,从顶部到叶子遍历它平均需要对数时间。这非常好! - 当然,你必须保持树是可变的。如果仅由一个节点表示的世界中无聊的部分发生了变化,则需要打开该节点并将其拆分为两个或多个子节点。如果之后再简单,你可以再次和孩子们一起砍树。

此时您可能注意到的一件事是:内存打包和访问优化在这里不再是真正的问题。像这样的树不能通过使用存储和查找方法的本机函数来合理优化。如果你能从这样的优化中获得超过 10% 的收益,那么这将是非常不可能的并且非常令人印象深刻。 (更可能的是,这可能意味着 Java/Scala 对应版本的优化很差。)如此最小的速度提升并不能证明需要付出巨大的额外努力。而是将更好的 CPU 放入机器中,享受通过吃冰淇淋、观看 Dr. House 或继续进一步增强游戏并使其对玩家更有趣和更具吸引力所节省的时间。通过创造能够真正改进产品的有价值的东西。

但这仍然不是它。如果我没记错的话,Minecraft 世界的初始状态是程序生成的。使用分形算法,您真的可以在眨眼间创造出让人感觉复杂和自然的无尽区域。因此,与其预先计算游戏世界的内容并将其存储在一个巨大的数据结构中,不如使用世界生成过程作为查找方法:与其从内存中查找坐标的内容,不如简单地使用算法。这样一来,世界的初始状态就可以完全存储在四个字节中:算法的种子值。

当然,世界不会一直停留在这种状态。当玩家(或其他东西)改变世界时,这就是你需要存储的东西。因此,您只存储世界的种子值和对其所做的更改。每当您查找坐标的内容时,请尝试在更改的图块存储中找到它。当它在那里时,使用该信息。当它不存在时,默认为程序世界生成算法。这将使您的内存消耗大大减少。而且由于对世界的更改相对较小并且包含巨大的空白区域,因此您应该相对容易编写一个快速有效地存储这些更改的数据结构。同样,为此编写本机代码不会产生显着的性能提升,也不值得付出努力。

还有一些东西可以优化:程序世界生成算法。这是您可能希望用 C 或 C++ 编写的一个关键组件。它应该相对较小且代码不多,但它是数学密集型的,并且会经常被调用。所以优化它并用它制作一个小型 JNI 库。这将为您带来巨大的性能提升,值得付出努力。 (当然,你可能想先做一个 Java/Scala 实现。如果这已经足够快,那么就没有必要陷入 JNI 的麻烦。)

如果你的世界生成过程仍然太慢,那么你可以为它实现一个缓存。当JVM有一些懒惰的时候,缓存甚至可以抢先生成玩家的一些环境。

我为您列出了这个开发过程,作为几个想法的迭代,其中一个比上一个更好。您可能已经在第一阶段就开始编写优化的 C/C++ 代码库了。这将是浪费时间;你本可以在后期把它全部扔掉。使用 C 语言编写的位打包的高效数组存储是一件好事,但当您将世界重新组织成二进制空间分区树时,它绝对没有用。

所以,不要过度。当您无法仅在 Java/Scala 中创建最低限度的工作(但缓慢且未优化)版本时,您也无法在 C/C++ 或某些交叉编译脚本语言中创建优化版本。先做简单版本,再做性能测试,真正需要的时候才优化。不要通过首先制定优化概念来开始您的项目。这种优化应该是您最后要做的事情。

【讨论】:

  • 哇,我真的让你去了那里。可能是我得到的最详细的答案。我希望我能给你超过一票。我将不得不制作几个 cmets,因为它不适合一个。但首先我不得不说我已经写了大约20页的“设计”,所以我已经想到了你说的很多东西。
  • “游戏世界可以很好地用一个三维数组来表示。访问这些数组相当快” 访问数组不是我主要关心的问题。我在家里设置了一个 Minecraft 服务器来和我的孩子一起玩。我们只有两个,还没有做那么多。尽管为客户端和服务器都提供了 1024MB,但我已经在该配置中获得了 OutOfMemory!这是我最担心的。
  • “创建一个可以创建 Java/Scala 和 C/C++ 版本的通用“脚本”源并没有真正意义”“脚本”也应该由“modders”编写。我也希望它们能够原生运行,但我不想让模组制作者直接执行 C++ 并由于错误的指针而导致服务器崩溃。
  • “创建或调整跨编程语言代码生成器的开销要大得多” 如果我使用其他人以前做过的代码生成器,然后将其删除,则不会。例如,解析器 Parboiled 使用 Java 6 语法作为示例。此外,如果您有自己的语言,则可以将其“编译”到 GPU!现在云服务器可以拥有 GPU,这将成为一个非常有趣的前景。
  • Matlab 和 Fortran 都对矩阵有更好的(本机)支持,这些矩阵很大但不会占用大量内存。 Matlab 和 Fortran 都支持 GPU 编程。
【解决方案3】:

JavaScript 语法类似于 C/C++ 和 Java。存在各种 JavaScript 引擎,用 Java 编码的引擎是Rhino

还有LLVM,它是一个编译器引擎,可以将代码编译成自己的字节码,然后再编译成机器码。它还集成了 JIT,并且存在许多语言前端。我对这个项目了解不多,但看起来很有趣。

【讨论】:

  • JavaScript 并不是真正的“类型化”。而且我怀疑它会比使用 LLVM 编译的 Java wen 运行得更快。
【解决方案4】:

那里有这样的语言吗? ...在 JVM 中执行此操作的最简单方法是什么,

我会使用纯 Java 并让 JVM 将代码编译为本机机器码。如果需要,可以使此编译更具侵略性。

也许您可以澄清一下您希望获得哪些 JVM 尚未提供给您的东西。


如果您真的想开发自己的迷你语言,那么您需要对需要做的事情有一个现实的想法。如果你不做出承诺,你很可能会想出一些不太好的东西。

http://www.ohloh.net/p/openjdk/estimated_cost

OpenJDK: Project Cost Calculator

Include     
Codebase    4,782,885 lines
Effort (est.)   1451 person-years
Estimated Cost  $ 79,805,125

【讨论】:

  • 我认为答案是 Java。 ;)
  • 我知道你的意思,但如果能有别的东西来作为基准,那就太好了……
  • 您可以比较 Java 和 C 的基准测试。Java 在大约 30% 的时间里更快(其余时间更慢)。恕我直言,差异可能很小,很难在此基础上证明努力的合理性。
  • 我真的不想开发自己的语言;我希望其他人以前做过。如果这是我唯一的选择,那么我会坚持使用 Scala/Java 并花更多时间在分析器上。
【解决方案5】:

由于 Scala 似乎是一种选择,所以坚持下去。

您可以直接从您自己的代码调用解释器(REPL 使用的),它将脚本编译为 Java 字节码,然后运行。您将很难找到在功能和灵活性方面与此匹配的解决方案 - 特别是考虑到静态类型的要求。

至于性能,JVM 负责进一步将字节码编译为本机代码,它也很擅长这项工作。我怀疑你会看到 C/C++ 的任何显着性能提升(尤其是编译时间会更糟)。

【讨论】:

  • 我刚刚发现了这个:github.com/sirthias/parboiled/wiki/Java-Parser Parboiled 库类似于 Scala DSL 语法,但具有更好的性能和错误消息。他们有一个用于 Java 的示例解析器,所以我可以把它去掉,然后让它输出 C++。
【解决方案6】:

我发现 Groovy 在 JVM 上作为类 C 脚本语言运行良好。

不确定将其转换为 C/C++ 的效果如何,但如果您有需要调用的 C/C++ 代码,则可以使用 JNI 轻松链接到它。

我认为将基于 JVM 的代码转换为 C/C++ 没有其他价值。对于明显的编译路径(JVM 语言 -> JVM 字节码 -> 本机),JVM JIT 已经是一个非常好的编译器,并且几乎肯定会击败任何试图做更复杂的东西(JVM 语言 -> C/C++)的性能-> 本机)。

【讨论】:

  • 我应该说我想要 C 的速度,这使 Groovy 失去了资格。
【解决方案7】:

JavaScript 呢?我没有满足你的所有要求(它只是弱类型),但它似乎满足了其中的不少:类 C、通常的控制语句,以及运算符、数组、函数,你可以提供自己的 API...它甚至可以编译成 Java,但现代解释器无论如何都非常擅长即时编译它。如果您使用 Java 编写,我强烈建议您查看Rhino

【讨论】:

    【解决方案8】:

    这是完整的 C,但可能对您的目的有用。 Cibyl.

    【讨论】:

    • 虽然 Cibyl 很有趣,但它是一种错误的方式。我想先编译成 Java,然后再编译成 C/C++,而不是反过来。原因是我不希望我的“用户”被要求安装 C/C++ 开发环境。
    【解决方案9】:

    在阅读了所有答案之后,这就是我决定做的事情。为了在我的项目中抢占先机,我将一开始就使用脚本。我要做的是用 Java 编写程序,但不使用任何有问题的结构。

    首先,我将创建一个 API 基类,其中包含我可能需要的任何实用方法。 95% 的代码只会委托给一些为我工作的库。这个 API 类将具有原始集合类的工厂方法。也将是一些实用程序类,如 Point、Event、...

    然后,我将通过扩展 API 对象来创建我的游戏代码。我不会使用访问修饰符,所以一切都是包公开的。我不会使用“final”或“static”或“this”或“super”或“abstract”或“interface”或数组或通用泛型或类转换或“instanceof”或“new”或异常处理。我不会导入任何东西或访问那个标准的 Java API,除了 String 类。我需要从标准 Java API 获得的任何东西都必须包含在我的 API 基类中。操作符也有一些限制,尤其是按位操作。

    为了克服不使用泛型或类转换或“instanceof”造成的限制,我将使用控制反转、依赖注入和一点反射来解决问题。

    这应该相对容易和快速编程,但不会执行。稍后,当我的引擎稳定并被调试后,我可以回到我最初的想法,并创建一个解析器,该解析器将转换为优化的 Java 或 C++。删除所有这些特殊 Java 结构将使构建解析器变得更加容易,并且由于我将使用标准 Java 语法,我可以只使用预定义的解析器,并根据我的需要对其进行自定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-29
      • 2014-11-04
      • 2011-01-04
      • 1970-01-01
      • 2010-10-13
      • 2014-05-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多