我经常需要在Haskell中执行模算术运算,其中模数通常很大且经常是质数(例如2000000011)。目前,我只使用像(modAdd m a b),(modMul m a b),(modDiv m a b)等函数。但这相当不方便,需要额外指定并携带一个附加参数,并在常规整数形式和单独的mod-形式中创建我的各种函数。
因此,创建一个新类似于以下内容的新类可能是一个好主意:
但是这里有个问题:所有类型为 Mod Int 的值必须有相同的模数。然而,通常我需要在一个程序中使用多个模数(当然,始终只组合相同模数的值)。
我认为可以通过类似下面的方法解决这个问题,但我不理解得足够好以确保这一点:
幕后会发生什么?特别是,一个包含一百万个[Mod Int 2000000011]元素的列表是否会携带额外的一百万个2000000011副本?还是它编译成与单独携带模参数的一百万个Int列表相同的代码?后者更好。
性能补充说明
我在解决当前问题时运行了一些基准测试。第一次运行使用未装箱的10,000元素Int向量,并对其执行了10,000次操作:
因此,创建一个新类似于以下内容的新类可能是一个好主意:
class Integral a => Mod a
m = 2000000011
instance Integral a => Num (Mod a) where
... defining (+), etc. as modulo m
然后可以使用常规函数执行常规算术,并定义有用的结构,例如
factorials :: [Mod Int]
factorials = 1:zipWith (*) factorials [1..]
但是这里有个问题:所有类型为 Mod Int 的值必须有相同的模数。然而,通常我需要在一个程序中使用多个模数(当然,始终只组合相同模数的值)。
我认为可以通过类似下面的方法解决这个问题,但我不理解得足够好以确保这一点:
class Integral a => Mod Nat a
其中Nat是一种以Peano方式编码模数的类型。这将是有利的:我可以拥有不同模数的值,类型检查器将防止我意外组合此值。
像这样的东西是否可行和有效?如果我尝试使用该模数,编译器或RTS是否会尝试实际构造巨大的(Succ(Succ(Succ ...重复2000000011次)),使解决方案实际上无用? RTS是否会在每个操作上尝试检查类型匹配?每个值的RTS表示是否会从本来可以只是未装箱的int被放大?
有更好的方法吗?
结论
感谢 cirdec、dfeuer、user5402 和 tikhon-jelvis 提供的有用评论,我意识到(并不出乎意料)我不是第一个想到这个想法的人。特别地,Kiselyov和Shan最近发表了一篇论文,给出了一种实现方法,而tikhon-jelvis已经在Hackage上发布了一个名为(惊喜!)模数算术的解决方案,使用高级的ghc pragma提供了更好的语义。未解决问题(对我来说)
我需要成为一个有用的助手来翻译文本。幕后会发生什么?特别是,一个包含一百万个[Mod Int 2000000011]元素的列表是否会携带额外的一百万个2000000011副本?还是它编译成与单独携带模参数的一百万个Int列表相同的代码?后者更好。
性能补充说明
我在解决当前问题时运行了一些基准测试。第一次运行使用未装箱的10,000元素Int向量,并对其执行了10,000次操作:
4,810,589,520 bytes allocated in the heap
107,496 bytes copied during GC
1,197,320 bytes maximum residency (1454 sample(s))
734,960 bytes maximum slop
10 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 6905 colls, 0 par 0.109s 0.101s 0.0000s 0.0006s
Gen 1 1454 colls, 0 par 0.812s 0.914s 0.0006s 0.0019s
TASKS: 13 (1 bound, 12 peak workers (12 total), using -N11)
SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
INIT time 0.000s ( 0.001s elapsed)
MUT time 2.672s ( 2.597s elapsed)
GC time 0.922s ( 1.015s elapsed)
EXIT time 0.000s ( 0.001s elapsed)
Total time 3.594s ( 3.614s elapsed)
Alloc rate 1,800,454,557 bytes per MUT second
Productivity 74.3% of total user, 73.9% of total elapsed
第二次运行,我在一个未装箱的大小为10,000的向量(Mod Int 1000000007)上执行了相同的操作。这使得我的代码变得更简单,但时间大约需要3倍长(同时具有几乎相同的内存配置文件):
4,810,911,824 bytes allocated in the heap
107,128 bytes copied during GC
1,199,408 bytes maximum residency (1453 sample(s))
736,928 bytes maximum slop
10 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 6906 colls, 0 par 0.094s 0.107s 0.0000s 0.0007s
Gen 1 1453 colls, 0 par 1.516s 1.750s 0.0012s 0.0035s
TASKS: 13 (1 bound, 12 peak workers (12 total), using -N11)
SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
INIT time 0.000s ( 0.001s elapsed)
MUT time 8.562s ( 8.323s elapsed)
GC time 1.609s ( 1.857s elapsed)
EXIT time 0.000s ( 0.001s elapsed)
Total time 10.172s ( 10.183s elapsed)
Alloc rate 561,858,315 bytes per MUT second
Productivity 84.2% of total user, 84.1% of total elapsed
我想知道为什么会发生这种情况,以及是否可以修复。尽管如此,我非常喜欢模数算术包,并将在性能不是绝对关键的情况下使用它。