Nim 枚举类型 对性能的影响

继上一篇文章《Nim 概念 Concept 对性能的影响》后,我在想,既然 method 虚方法造成性能的影响很大,那么有没有更快的方法实现,当然是有的,那就是枚举类型。

Enum Type

与很多的新设计的一样,Nim语言也内置了枚举类型,比如下面的代码:

type
ValueGetterKind = enum
vkClass1,
vkClass2
ValueGetter2 = ref object
sharedField : int32
case kind:ValueGetterKind
of vkClass1:someField1 : int32
of vkClass2:someField2 : int32

正如你看到的,在Nim语言中,通过为类型增加一个枚举字段(这里是kind),就可以为不同的类型定义不同的字段了,当然,你也可以定义共享的字段(这里例子的 sharedField)。

官方的例子中,演示了更复杂的情况,比如 多个类型公用相同的字段,或者定义多个字段。

# This is an example how an abstract syntax tree could be modelled in Nim
type
NodeKind = enum # the different node types
nkInt, # a leaf with an integer value
nkFloat, # a leaf with a float value
nkString, # a leaf with a string value
nkAdd, # an addition
nkSub, # a subtraction
nkIf # an if statement
Node = ref object
case kind: NodeKind # the `kind` field is the discriminator
of nkInt: intVal: int
of nkFloat: floatVal: float
of nkString: strVal: string
of nkAdd, nkSub:
leftOp, rightOp: Node
of nkIf:
condition, thenPart, elsePart: Node var n = Node(kind: nkFloat, floatVal: 1.0)
# the following statement raises an `FieldDefect` exception, because
# n.kind's value does not fit:
n.strVal = ""

性能对比

接着我们之前的例子,我们看看在泛型的情况下,调用枚举类型对性能究竟影响有多少。

import std/[times],std/random
type
IValueGetter = concept s
s.getValue(int32) is int64 MyTestClass[T] = object
valueGetter: T proc run[T: IValueGetter](this: MyTestClass[T]): int64 =
var r = 0i64
let n = high(int32) - rand(100).int32
for i in 0 ..< n:
r += this.valueGetter.getValue(i)
return r type
ValueGetterKind = enum
vkClass1,
vkClass2,
#vkClass3,
ValueGetter2 = ref object
sharedField : int32
case kind:ValueGetterKind
of vkClass1:someField1 : int32
of vkClass2:someField2 : int32
#of vkClass3:someField3 : int32 func getValue(this: ValueGetter2, index: int32): int64 =
#return index.int64 + 7i64
case this.kind:
of vkClass1:
result = index.int64 + 9i64
of vkClass2:
result = index.int64 + 11i64
#of vkClass3:
# result = index.int64 + 17i64 proc measureTime(caption: string, procToMeasure: proc(): int64) =
var startTime = cpuTime()
let r = procToMeasure()
var endTime = cpuTime()
echo caption, " time = ", endTime - startTime, " result = ", r # 运行测试
proc main() =
randomize() let t7 = MyTestClass[ValueGetter2](valueGetter : ValueGetter2(kind:vkClass1, someField1:9))
measureTime("ValueGetter2:1 ", proc ():int64 = t7.run())
let t8 = MyTestClass[ValueGetter2](valueGetter : ValueGetter2(kind:vkClass2, someField2:11))
measureTime("ValueGetter2:2 ", proc ():int64 = t8.run())
#let t9 = MyTestClass[ValueGetter2](valueGetter : ValueGetter2(kind:vkClass3, someField3:11))
#measureTime("ValueGetter2:3 ", proc ():int64 = t9.run()) when isMainModule:
main()

在我的机器中,测试结果如下(均使用release编译,下同):

ValueGetter2:1     time = 0.725 result = 2305842980222664759
ValueGetter2:2 time = 1.967 result = 2305842982370148375

我们看到,如果是判断分支的第一个,性能与本地调用稍差(0.7对比0.4),但与虚方法的5秒已经好太多。

细心的你,可能已经注意到我给的代码注释掉了第三种类型vkClass3,那么是不是作为分支的第三种情况,会更慢?让我们取消几个分支的注释,看看结果:

ValueGetter2:1     time = 1.300 result = 2305842894323320179
ValueGetter2:2 time = 1.345 result = 2305842842783714180
ValueGetter2:3 time = 1.295 result = 2305842993107566484

意不意外?第一个分支竟然变慢了,而后两个分支变快了,最后一个分支还稍稍快一点点。是否说明nim语言编译为2个枚举类型和3个枚举类型,使用了不同的编译代码? 让我们从nim生成的c语言源码验证这件事。

// 这是2个枚举的代码
N_LIB_PRIVATE N_NIMCALL(NI64, getValue__demo50_u28)(tyObject_ValueGetter2colonObjectType___liWfIs6eMPnbT0BuR4LSgg* this_p0, NI32 index_p1) {
NI64 result;
result = (NI64)0;
switch ((*this_p0).kind) {
case ((tyEnum_ValueGetterKind__tsIJjFnfs8zo9caQCLYJn1A)0):
{
result = (NI64)(((NI64) (index_p1)) + IL64(9));
}
break;
case ((tyEnum_ValueGetterKind__tsIJjFnfs8zo9caQCLYJn1A)1):
{
result = (NI64)(((NI64) (index_p1)) + IL64(11));
}
break;
}
return result;
}
// 这是三个枚举的代码
N_LIB_PRIVATE N_NIMCALL(NI64, getValue__demo50_u30)(tyObject_ValueGetter2colonObjectType___liWfIs6eMPnbT0BuR4LSgg* this_p0, NI32 index_p1) {
NI64 result;
result = (NI64)0;
switch ((*this_p0).kind) {
case ((tyEnum_ValueGetterKind__tsIJjFnfs8zo9caQCLYJn1A)0):
{
result = (NI64)(((NI64) (index_p1)) + IL64(9));
}
break;
case ((tyEnum_ValueGetterKind__tsIJjFnfs8zo9caQCLYJn1A)1):
{
result = (NI64)(((NI64) (index_p1)) + IL64(11));
}
break;
case ((tyEnum_ValueGetterKind__tsIJjFnfs8zo9caQCLYJn1A)2):
{
result = (NI64)(((NI64) (index_p1)) + IL64(17));
}
break;
}
return result;
}

可以看出,代码完全是一样的,可能是编译器做了手脚。当然,我也测试了更多枚举的可能,比如7种,我发现所有分支时间都变长了(达到2秒),但总体是比虚方法好的。

共享的字段

在 getValue 方法中,如果你没有对类型进行判断,那么实际测试的成绩和静态方法调用是一样的,这可以被利用到我们面向对象编程中,经常用到的基类统一实现的场景。

总结

在 nim 编程中,简单的派生关系可以使用 枚举类型来提高性能。

Nim 枚举类型 对性能的影响的更多相关文章

  1. (转)类(class)和结构(struct)的区别是什么?它们对性能有影响吗?.NET BCL里有哪些是类(结构),为什么它们不是结构(类)?在自定义类型时,您如何选择是类还是结构?

    转自:http://blog.csdn.net/lingxyd_0/article/details/8695747 类(class)和结构(struct)的区别是什么?它们对性能有影响吗?.NET B ...

  2. 一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)

    这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1.简述MySQL中索引类型对数据库的性能的影响 2.RDB和AOF机制 3.Redis的过期键的删除策略 4.Redis ...

  3. Enum(枚举类型)的基本应用

    一.前言 在我们日常的开发过程中,我们经常定义使用常量:在Effective Java建议用枚举来替换常量的使用,提高我们代码的质量,总结一下枚举定义常量的基本使用 二.枚举类型说明      1.枚 ...

  4. 【转】Android中的内存管理--不错不错,避免使用枚举类型

    原文网址:http://android-performance.com/android/2014/02/17/android-manage-memory.html 本文内容翻译自:http://dev ...

  5. List是线程安全的吗?如果不是该怎么办呢?安全的List对性能的影响有多大呢?

    测试条件: 开启2个并行执行任务,往同一个list对象写入值 测试代码: ; static List<int> list = new List<int>(); static v ...

  6. Effective Java 第三版——34. 使用枚举类型替代整型常量

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  7. 结构体struct、联合体union、枚举类型enum

    1.c语言中的类型 1)内置类型——char,short,int,float,double: 2)用户自定义类型(UDT)——struct结构体,union联合体,enum枚举类型 2.内存对齐 2. ...

  8. Python 中的枚举类型~转

    Python 中的枚举类型 摘要: 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期.月份.状态等. 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表 ...

  9. 从一个int值显示相应枚举类型的名称或者描述

    我正在做一个出入库管理的简单项目,在Models里定义了这样的枚举类型 public enum InOrOut { [Description("出库")] Out = , [Des ...

  10. C语言--enum,typedef enum 枚举类型详解

    原文:http://z515256164.blog.163.com/blog/static/32443029201192182854300/ 有改动 C语言详解 - 枚举类型 注:以下全部代码的执行环 ...

随机推荐

  1. 全局多项式(趋势面)与IDW逆距离加权插值:MATLAB代码

      本文介绍基于MATLAB实现全局多项式插值法与逆距离加权法的空间插值的方法,并对不同插值方法结果加以对比分析. 目录 1 背景知识 2 实际操作部分 2.1 空间数据读取 2.2 异常数据剔除 2 ...

  2. ATtiny88初体验(七):TWI

    ATtiny88初体验(七):TWI TWI模块介绍 ATtiny88的TWI模块兼容Phillips I2C以及SMBus,支持主从模式,支持7bit地址,最大允许128个不同的从机地址.在多主机模 ...

  3. 文心一言 VS 讯飞星火 VS chatgpt (95)-- 算法导论9.2 4题

    四.用go语言,假设用RANDOMIZED-SELECT 去选择数组 A=(3,2,9,0,7,5,4,8,6,1)的最小元素,给出能够导致 RANDOMIZED-SELECT最坏情况发生的一个划分序 ...

  4. dedebiz 清理冗余废弃未引用图片方法

    原理描述: 在原有织梦后台菜单中增加"清理冗余图片按钮",实现清理冗余图片的功能. 操作步骤: 1. 打开后台admin\sys_sql_query.php代码 在该文件中搜索如下 ...

  5. RK3568开发笔记(十一):开发版buildroot固件移植一个ffmpeg播放rtsp的播放器Demo

    前言   目标开发任务还有个功能,就是播放rtsp摄像头,当然为了更好的坐这个个,我们必须支持rtsp播放失败之后重新尝试,比如5s重新尝试打开一次,从而保障联网后重新打开,然后达成这个功能.   D ...

  6. webgl centroid质心插值的一点理解

    质心插值说的是什么 2023.10.04再次review这个细节点: https://www.opengl.org/pipeline/article/vol003_6/ https://github. ...

  7. c语言代码练习1

    //输出1-n阶乘的和,n为输入值//计算n的阶乘可以使用for循环,也可使用while循环#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h ...

  8. 详解RecyclerView的预布局

    概述 RecyclerView 的预布局用于 Item 动画中,也叫做预测动画.其用于当 Item 项进行变化时执行的一次布局过程(如添加或删除 Item 项),使 ItemAnimator 体验更加 ...

  9. 这款 7k Star 的国产监控系统,真不错!

    我们都知道天下没有"永不宕机"的系统,但每次线上出问题都要拉出一个程序员"祭天".所以一款靠谱.好用的监控工具就显得十分重要,它可以在生产环境出故障的第一时间发 ...

  10. FFMPEG+SDL简单视频播放器——视频快进

    之前写过一篇关于视频播放器的文章.播放器只简单实现了视频播放的功能,在此功能的基础上,给它加上一个视频快进的功能. 实现 添加参数 // video play control bool do_seek ...