perl中的基本类型与大多数语言不同,类型是标量,数组,哈希(但显然不是子例程,而且,我猜它实际上只是带语法糖的标量引用).最奇怪的是最常见的数据类型:int,boolean,char,string,都属于基本数据类型"标量".似乎perl决定将标量视为字符串,布尔值或基于修改它的运算符的数字,这意味着标量本身在保存时实际上并未定义为"int"或"String".
这让我很好奇这些标量是如何存储在"引擎盖下"的,尤其是它对效率的影响(是的,我知道脚本语言会牺牲灵活性的效率,但是当灵活性问题时,它们仍然需要尽可能优化没有受到影响).我更容易存储数字65535(需要两个字节)然后字符串"65535",需要6个字节,因此认识到$ val = 65535存储一个int将允许我使用1/3的内存,在大型阵列中,这也意味着更少的缓存命中.
当然,它不仅限于节省内存.如果我知道期望什么类型的标量,有时我可以提供更重要的优化.例如,如果我有一个使用非常大的整数作为键的哈希值,那么如果我将键识别为int,那么查找值会快得多,允许一个简单的模数来创建我的哈希键,然后如果我必须运行更复杂的哈希值字符串上的逻辑,具有3倍的字节.
所以我想知道perl如何在引擎盖下处理这些标量.它是否将每个值存储为字符串,在标量始终用作int的情况下,牺牲额外的内存和常量将字符串转换为int的cpu成本?或者它是否有一些逻辑推断用于确定如何保存和操纵它的标量类型?
编辑:
TJD与perlguts相关联,这回答了我的一半问题.标量实际上存储为字符串,int(有符号,无符号,双精度)或指针.我并不太惊讶,我大多数人都期望这种行为发生在引擎盖下,尽管看到确切的类型很有意思.我打开这个问题,因为perlguts实际上是低级别的.然后告诉我存在5种数据类型,它没有指定perl如何在它们之间交替工作,即perl如何决定在保存标量时使用哪种SV类型以及它如何知道何时/如何投射.
ike*_*ami 13
实际上有许多类型的标量.类型的标量SVt_IV可以包含undef,有符号整数(IV)或无符号整数(UV).其中一种类型SVt_PVIV也可以包含一个字符串[1].根据需要,标量可以从一种类型静默升级到另一种类型[2].该TYPE字段表示标量的类型.实际上,arrays(SVt_AV)和hashes(SVt_HV)实际上只是标量的类型.
虽然标量的类型指明了标量可以包含标志用来指示什么标不包含.这存储在FLAGS字段中.标志SVf_IOK包含有符号整数的信号,同时SVf_POK表示它包含一个字符串[3].
杰韦利::皮克的Dump是看标量的内部一个伟大的工具.(常量前缀SVt_,SVf_省略Dump.)
$ perl -e'
use Devel::Peek qw( Dump );
my $x = 123;
Dump($x);
$x = "456";
Dump($x);
$x + 0;
Dump($x);
'
SV = IV(0x25f0d20) at 0x25f0d30 <-- SvTYPE(sv) == SVt_IV, so it can contain an IV.
REFCNT = 1
FLAGS = (IOK,pIOK) <-- IOK: Contains an IV.
IV = 123 <-- The contained signed integer (IV).
SV = PVIV(0x25f5ce0) at 0x25f0d30 <-- The SV has been upgraded to SVt_PVIV
REFCNT = 1 so it can also contain a string now.
FLAGS = (POK,IsCOW,pPOK) <-- POK: Contains a string (but no IV since !IOK).
IV = 123 <-- Meaningless without IOK.
PV = 0x25f9310 "456"\0 <-- The contained string.
CUR = 3 <-- Number of bytes used by PV (not incl \0).
LEN = 10 <-- Number of bytes allocated for PV.
COW_REFCNT = 1
SV = PVIV(0x25f5ce0) at 0x25f0d30
REFCNT = 1
FLAGS = (IOK,POK,IsCOW,pIOK,pPOK) <-- Now contains both a string (POK) and an IV (IOK).
IV = 456 <-- This will be used in numerical contexts.
PV = 0x25f9310 "456"\0 <-- This will be used in string contexts.
CUR = 3
LEN = 10
COW_REFCNT = 1
Run Code Online (Sandbox Code Playgroud)
illguts非常彻底地记录了变量的内部格式,但是perlguts可能是一个更好的起点.
如果您开始编写XS代码,请记住检查标量包含的内容通常是个坏主意.相反,您应该请求应该提供的内容(例如使用SvIV或SvPVutf8).Perl会自动将值转换为请求的类型(并在适当时发出警告).perlapi中记录了API调用.
实际上,它可以同时保存字符串有符号整数或无符号整数.
所有标量(包括数组和散列,不包括一种只能容纳undef的标量)在它们的底部有两个内存块.指向标量的指针指向其头部,其中包含TYPE字段和指向身体的指针.升级标量会替换标量的主体.这样,标量指针不会因升级而失效.
undef变量是没有OK设置任何大写标志的变量.
dus*_*uff 11
perl用于数据存储的格式记录在perlgutsperldoc中.
简而言之,Perl标量存储为SV包含许多不同类型之一的结构,例如a int,a double,a char *或指向另一个标量的指针.(这些类型存储为C union,因此一次只能存在其中一个; SV包含指示使用哪种类型的标志.)
(关于散列键,有一个重要的问题需要注意:散列键总是字符串,并且总是存储为字符串.它们存储在与其他标量不同的类型中.)
Perl API包含许多函数,可用于访问标量值作为所需的C类型.例如,SvIV()可以用来返回SV的整数值:如果SV包含一个int,则直接返回该值; 如果SV包含另一种类型,则根据需要强制转换为整数.这些函数在整个Perl解释器中用于类型转换.但是,输出中没有类型的自动推断; 例如,对字符串进行操作的函数将始终返回PV(字符串)标量,无论字符串"看起来像"是否为数字.
如果您对内部给定标量的内容感到好奇,可以使用该Devel::Peek模块转储其内容.
其他人已经解决了"如何存储标量"的问题,所以我会跳过它.关于Perl如何决定使用哪个值的表示以及何时在它们之间进行转换,答案是它取决于哪些运算符应用于标量.例如,给定此代码:
my $score = 0;
Run Code Online (Sandbox Code Playgroud)
标量$score将用整数值初始化.但是当这行代码运行时:
say "Your score is $score";
Run Code Online (Sandbox Code Playgroud)
双引号运算符意味着Perl将需要值的字符串表示.因此,从整数到字符串的转换将作为将字符串参数组装到say函数的过程的一部分进行.有趣的是,字符串化后$score,标量的基本表示现在将包括两个整数和字符串表示,从而允许后续的操作直接抓住的相关值,而无需再次进行转换.如果随后将数字运算符应用于字符串(例如$score++:),则将更新数字部分,并且将丢弃(现在无效的)字符串部分.
这就是Perl运营商倾向于两种口味的原因.例如比较数字的值与完成<,==,>而用绳子执行相同的比较将与完成lt,eq,gt.Perl会将标量的值强制转换为与运算符匹配的类型.这就是+运算符在Perl中进行数字加法的原因,但是.需要一个单独的运算符来进行字符串连接:+将其参数.强制转换为数值并强制转换为字符串.
有些运算符可以使用数值和字符串值,但根据值的类型执行不同的操作.例如:
$score = 0;
say ++$score; # 1
say ++$score; # 2
say ++$score; # 3
$score = 'aaa';
say ++$score; # 'aaa'
say ++$score; # 'aab'
say ++$score; # 'aac'
Run Code Online (Sandbox Code Playgroud)
关于效率问题(并考虑到关于过早优化的标准免责声明等).考虑这个代码,它读取每行包含一个整数的文件,每个整数都经过验证,检查它是否正好是8位数,有效的存储在数组中:
my @numbers;
while(<$fh>) {
if(/^(\d{8})$/) {
push @numbers, $1;
}
}
Run Code Online (Sandbox Code Playgroud)
从文件读取的任何数据最初都将作为字符串发送给我们.用于验证数据的正则表达式还需要一个字符串值$_.结果是我们的数组@numbers将包含一个字符串列表.但是,如果值的进一步使用仅在数值上下文中,我们可以使用此微优化来确保数组仅包含数值:
push @numbers, 0 + $1;
Run Code Online (Sandbox Code Playgroud)
在我使用10,000行文件的测试中,@numbers使用字符串填充的内存几乎是填充整数值的内存的三倍.然而,与大多数基准测试一样,这与Perl中的正常日常编码几乎没有关系.在a)出现性能或内存问题并且b)处理大量值时,您只需要担心这种情况.
值得指出的是,其中一些行为对于其他动态语言来说很常见(例如:Javascript会默默地将数值强制转换为字符串).