Common Lisp中的对象内存布局 [英] Object memory layout in Common Lisp

查看:23
本文介绍了Common Lisp中的对象内存布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道Common Lisp不鼓励程序员接触原始内存,但我想知道是否可以查看对象是如何在字节级别存储的。当然,垃圾收集器在内存空间中移动对象,函数(obj-as-bytes obj)的两次后续调用可能会产生不同的结果,但让我们假设我们只需要一个内存快照。您将如何实现此类功能?

我尝试使用SBCL的情况如下:

(defun obj-as-bytes (obj)
  (let* ((addr (sb-kernel:get-lisp-obj-address obj)) ;; get obj address in memory
         (ptr (sb-sys:int-sap addr))                 ;; make pointer to this area
         (size (sb-ext:primitive-object-size obj))   ;; get object size 
         (output))
    (dotimes (idx size)
      (push (sb-sys:sap-ref-64 ptr idx) output))     ;; collect raw bytes into list
    (nreverse output)))                              ;; return bytes in the reversed order

我们试试:

(obj-as-bytes #(1)) =>
(0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 111 40 161 4 16 0 0 0 23 1 16 80 0 0 0)

(obj-as-bytes #(2) =>
(0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 95 66 161 4 16 0 0 0 23 1 16 80 0 0 0)

从该输出中我得出结论,其中有很多垃圾,这些垃圾占用了将来内存分配的空间。我们之所以看到它,是因为(sb-ext:primitive-object-size obj)似乎返回了足以容纳该对象的内存块。

此代码演示了它:

(loop for n from 0 below 64 collect
  (sb-ext:primitive-object-size (make-string n :initial-element #a))) =>
(16 32 32 32 32 48 48 48 48 64 64 64 64 80 80 80 80 96 96 96 96 112 112 112 112                                                                                                                               128 128 128 128 144 144 144 144 160 160 160 160 176 176 176 176 192 192 192                                                                                                                                     192 208 208 208 208 224 224 224 224 240 240 240 240 256 256 256 256 272 272                                                                                                                                     272)
因此,如果sb-ext:primitive-object-size更准确,obj-as-bytes会给出正确的结果。但我找不到其他选择。

您是否有任何建议如何修复此函数或如何以不同方式实现它?

推荐答案

正如我在一条评论中提到的,内存中对象的布局是非常特定于实现的,用于研究它的工具也必须依赖于实现。

此答案讨论了64位版本的SBCL的布局,并且仅针对具有"宽固定号"的64位版本。我不确定这两个东西到达SBCL的顺序,因为早在SBCL和CMUCL分道扬镳之前,我就没有认真考虑过这些问题。

此答案也可能是错误的:我不是SBCL开发人员,我之所以添加它,只是因为没有人会(我怀疑正确地标记问题可能会对此有所帮助)。

下面的信息来自查看the GitHub mirror,它似乎与canonical source非常同步,但速度要快得多。

指针、即时对象、标记

[来自here的信息。]SBCL在两个字的边界上进行分配。在64位系统上,这意味着任何地址的低四位始终为零。这低4位用作标记(文档将其称为"低标记"),以告诉您单词的睡觉中包含什么内容。

  • xyz0的低标记表示字的睡觉是fixnum,具体地说,xyz将是fixnum的低位,而不是标记位。这意味着有63位可用于Fixnumber,而且Fixnum添加非常简单:您不需要屏蔽任何位。
  • XY01的低位标记表示单词的睡觉是其他直接宾语。低位标记右边的一些位(我认为SBCL称之为‘widesg’,虽然我对此感到困惑,因为这个词似乎有两种用法)会说出直接宾语是什么。立即对象的示例是字符和单浮动(在64位平台上!)。
  • 其余的低标记模式是XY11,它们都表示对象是指向某个非直接对象的指针:
  • 0011是某物的实例;
  • 0111是缺点;
  • 1011是函数;
  • 1111是其他内容。

总结

因为cones不需要任何额外的类型信息(cons就是cons),所以低标记就足够了:cons在内存中只有两个单词,而每个单词又有低标记&;c。

其他非直接对象

我认为(但不确定)所有其他非直接宾语都有一个词来说明它们是什么(也可以称为‘widesg’)和至少一个其他词(因为分配是在两个词的边界上)。我怀疑函数的特殊标记意味着函数调用可以直接跳到函数代码的入口点。

查看此内容

room.lisp有一个很好的函数,名为hexdump,它知道如何打印非直接对象。在此基础上,我写了一个小垫片(下面),试图告诉您一些有用的东西。以下是一些示例。

> (hexdump-thing 1)
lowtags:    0010
fixnum:     0000000000000002 = 1

1是固定数,如上所述,其表示只是向右移动了一位。请注意,在本例中,低标记实际上包含整个值!

> (hexdump-thing 85757)
lowtags:    1010
fixnum:     0000000000029DFA = 85757

..。但在这种情况下不是这样。

> (hexdump-thing #c)
lowtags:    1001
immediate:  0000000000006349 = #c
> (hexdump-thing 1.0s0)
lowtags:    1001
immediate:  3F80000000000019 = 1.0

字符和单个浮点数是直接的:我想低标记左边的一些位告诉系统它们是什么?

> (hexdump-thing '(1 . 2))
lowtags:    0111
cons:       00000010024D6E07 : 00000010024D6E00
10024D6E00: 0000000000000002 = 1
10024D6E08: 0000000000000004 = 2
> (hexdump-thing '(1 2 3))
lowtags:    0111
cons:       00000010024E4BC7 : 00000010024E4BC0
10024E4BC0: 0000000000000002 = 1
10024E4BC8: 00000010024E4BD7 = (2 3)

Conses。在第一种情况下,您可以在cons的两个字段中看到作为立即值的两个固定数。在第二个字段中,如果您解码第二个字段的低位标记,它将是0111:这是另一个缺点。

> (hexdump-thing "")
lowtags:    1111
other:      00000010024FAE8F : 00000010024FAE80
10024FAE80: 00000000000000E5
10024FAE88: 0000000000000000 = 0
> (hexdump-thing "x")
lowtags:    1111
other:      00000010024FC22F : 00000010024FC220
10024FC220: 00000000000000E5
10024FC228: 0000000000000002 = 1
10024FC230: 0000000000000078 = 60
10024FC238: 0000000000000000 = 0
> (hexdump-thing "xyzt")
lowtags:    1111
other:      00000010024FDDAF : 00000010024FDDA0
10024FDDA0: 00000000000000E5
10024FDDA8: 0000000000000008 = 4
10024FDDB0: 0000007900000078 = 259845521468
10024FDDB8: 000000740000007A = 249108103229

字符串。它们有一些类型信息、一个长度字段,然后字符被打包成两个字。单字符字符串需要四个单词,与四个字符的字符串相同。您可以从数据中读取字符代码。

> (hexdump-thing #())
lowtags:    1111
other:      0000001002511C3F : 0000001002511C30
1002511C30: 0000000000000089
1002511C38: 0000000000000000 = 0
> (hexdump-thing #(1))
lowtags:    1111
other:      00000010025152BF : 00000010025152B0
10025152B0: 0000000000000089
10025152B8: 0000000000000002 = 1
10025152C0: 0000000000000002 = 1
10025152C8: 0000000000000000 = 0
> (hexdump-thing #(1 2))
lowtags:    1111
other:      000000100252DC2F : 000000100252DC20
100252DC20: 0000000000000089
100252DC28: 0000000000000004 = 2
100252DC30: 0000000000000002 = 1
100252DC38: 0000000000000004 = 2
> (hexdump-thing #(1 2 3))
lowtags:    1111
other:      0000001002531C8F : 0000001002531C80
1002531C80: 0000000000000089
1002531C88: 0000000000000006 = 3
1002531C90: 0000000000000002 = 1
1002531C98: 0000000000000004 = 2
1002531CA0: 0000000000000006 = 3
1002531CA8: 0000000000000000 = 0
对于简单的向量也有同样的处理:标题、长度,但现在每个条目当然需要一个单词。最重要的是,所有条目都是固定数字,您可以在数据中看到它们。

就这样继续下去。


执行此操作的代码

这个可能是错误的,它的早期版本肯定不喜欢小的大数(我认为hexdump不喜欢它们)。如果您想要真实的答案,可以阅读源代码或询问SBCL人员。其他实现可用,并且将有所不同。

(defun hexdump-thing (obj)
  ;; Try and hexdump an object, including immediate objects.  All the
  ;; work is done by sb-vm:hexdump in the interesting cases.
  #-(and SBCL 64-bit)
  (error "not a 64-bit SBCL")
  (let* ((address/thing (sb-kernel:get-lisp-obj-address obj))
         (tags (ldb (byte 4 0) address/thing)))
    (format t "~&lowtags: ~12T~4,'0b~%" tags)
    (cond
      ((zerop (ldb (byte 1 0) tags))
       (format t "~&fixnum:~12T~16,'0x = ~S~%" address/thing obj))
      ((= (ldb (byte 2 0) tags) #b01)
       (format t "~&immediate:~12T~16,'0x = ~S~%" address/thing obj))
      ((= (ldb (byte 2 0) tags) #b11)   ;must be true
       (format t "~&~A:~12T~16,'0x : ~16,'0x~%"
               (case (ldb (byte 2 2) tags)
                 (#b00 "instance")
                 (#b01 "cons")
                 (#b10 "function")
                 (#b11 "other"))
               address/thing (dpb #b0000 (byte 4 0) address/thing))
       ;; this tells you at least something (and really annoyingly
       ;; does not pad addresses on the left)
       (sb-vm:hexdump obj))
      ;; can't happen
      (t (error "mutant"))))
  (values))

这篇关于Common Lisp中的对象内存布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆