perl中的基本类型与大多数语言不同,类型是标量,数组,哈希(但显然不是子例程,&amp ;,我猜它实际上只是带语法糖的标量引用)。最奇怪的是最常见的数据类型: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类型以及它如何知道何时/如何投射。
答案 0 :(得分:12)
实际上有许多类型的标量。类型SVt_IV
的标量可以包含undef,有符号整数(IV
)或无符号整数(UV
)。类型SVt_PVIV
之一也可以包含字符串 [1] 。根据需要,标量会从一种类型静默升级到另一种类型 [2] 。 TYPE
字段表示标量的类型。实际上,数组(SVt_AV
)和哈希(SVt_HV
)实际上只是标量的类型。
虽然标量的类型表示标量可以包含的内容,但标志用于指示标量 包含的内容。这存储在FLAGS
字段中。 SVf_IOK
表示标量包含有符号整数,而SVf_POK
表示它包含字符串 [3] 。
Devel::Peek的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
illguts非常彻底地记录了变量的内部格式,但perlguts可能是一个更好的起点。
如果您开始编写XS代码,请记住检查标量包含的内容通常是个坏主意。相反,您应该请求应该提供的内容(例如,使用SvIV
或SvPVutf8
)。 Perl会自动将值转换为请求的类型(并在适当时发出警告)。 API调用记录在perlapi。
实际上,它可以同时将字符串保存为有符号整数或无符号整数。
所有标量(包括数组和散列,不包括只能容纳undef的一种标量)在其基数上有两个内存块。指向标量的指针指向 head ,其中包含TYPE
字段和指向 body 的指针。升级标量会替换标量的主体。这样,标量的指针不会因升级而失效。
undef变量是没有设置任何大写OK
标志的变量。
答案 1 :(得分:10)
Perl用于数据存储的格式记录在perlguts
perldoc。
简而言之,Perl标量存储为SV
结构,其中包含多种不同类型之一,例如int
,double
,{{1或者指向另一个标量的指针。 (这些类型存储为C char *
,因此一次只能存在其中一个; SV包含指示使用哪种类型的标志。)
(关于散列键,有一个重要的问题需要注意:散列键总是字符串,并且总是存储为字符串。它们存储在与其他标量不同的类型中。 )
Perl API包含许多函数,可用于访问标量值作为所需的C类型。例如,union
可用于返回SV的整数值:如果SV包含SvIV()
,则直接返回该值;如果SV包含另一种类型,则根据需要强制转换为整数。这些函数在整个Perl解释器中用于类型转换。但是,输出中没有类型的自动推断;例如,对字符串进行操作的函数将始终返回int
(字符串)标量,无论字符串“看起来像”是否为数字。
如果您对内部给定标量的内容感到好奇,可以使用Devel::Peek
模块转储其内容。
答案 2 :(得分:5)
其他人已经解决了“如何存储标量”的问题,所以我会跳过这个问题。关于Perl如何决定使用哪个值的表示以及何时在它们之间进行转换,答案是它取决于哪些运算符应用于标量。例如,给定此代码:
my $score = 0;
标量$score
将使用整数值进行初始化。但是当这行代码运行时:
say "Your score is $score";
双引号运算符意味着Perl将需要值的字符串表示。因此,从整数到字符串的转换将作为将字符串参数组装到say
函数的过程的一部分进行。有趣的是,在$score
的字符串化之后,标量的基础表示现在将包括 整数和字符串表示,允许后续操作直接获取相关值,无需再次转换。如果数字运算符随后应用于字符串(例如:$score++
),则数字部分将被更新,并且(现在无效的)字符串部分将被丢弃。
这就是为什么Perl运营商倾向于两种口味的原因。例如,使用<
,==
,>
比较数字值,同时使用lt
,eq
,{gt
执行相同的字符串比较{1}}。 Perl会将标量的值强制转换为与运算符匹配的类型。这就是+
运算符在Perl中进行数字加法的原因,但是需要单独的运算符.
来进行字符串连接:+
将强制其参数转换为数值,.
将强迫到字符串。
有些运算符可以使用数值和字符串值,但根据值的类型执行不同的操作。例如:
$score = 0;
say ++$score; # 1
say ++$score; # 2
say ++$score; # 3
$score = 'aaa';
say ++$score; # 'aaa'
say ++$score; # 'aab'
say ++$score; # 'aac'
关于效率问题(并考虑到关于过早优化的标准免责声明等)。考虑这个代码,它读取每行包含一个整数的文件,每个整数都经过验证,检查它是否正好是8位数,有效的存储在数组中:
my @numbers;
while(<$fh>) {
if(/^(\d{8})$/) {
push @numbers, $1;
}
}
从文件读取的任何数据最初都将作为字符串发送给我们。用于验证数据的正则表达式还需要$_
中的字符串值。结果是我们的数组@numbers
将包含一个字符串列表。但是,如果值的进一步使用仅在数值上下文中,我们可以使用此微优化来确保数组仅包含数值:
push @numbers, 0 + $1;
在我使用10,000行文件的测试中,使用字符串填充@numbers
使用的内存几乎是填充整数值的内存的三倍。然而,与大多数基准测试一样,这与Perl中的正常日常编码几乎没有关系。在a)遇到性能或内存问题并且b)处理大量值时,你只需要担心这一点。
值得指出的是,这种行为中的一些是其他动态语言所共有的(例如:Javascript会默默地将数值强制转换为字符串)。