Automated Object Data Compaction Revisited

by Finnian Reilly (modified: 2024 Oct 16)

Introduction

In a previous 2023 article titled Automating Object Data Compaction to Expanded Numeric Types, I presented a method to automate the compression of object data attributes into a single numeric type.

The standard ISE DATE class is an example of manually implemented data compaction to a numeric type. The features year, month and day are compacted into a single 32-bit integer ordered_compact_date.

What I would like present in this article is a more sophisticated form of data compaction based on the concept of subrange types as exemplified by the Pascal language. (Hat-tip to Ian Joyner for giving me this idea) What this entails is a way to specify the possible range of values for a class attribute, and then a reflection mechanism that calculates the optimum number of bits to represent that range.

Explicit Bit Masks

The original 2023 article presented a date class with an explicit mapping of attributes to bit masks specified in a string manifest. The Range_table constant explicitly maps date fields to a range of bits. The range 1 .. 8 for example represents the first 8 bits.

inherit EL_COMPACTABLE_REFLECTIVE rename compact_integer_32 as compact_date, make_from_integer_32 as make_from_compact_date, set_from_integer_32 as set_from_compact_date redefine Range_table end create make, make_from_compact_date feature {NONE} -- Initialization make (a_year: INTEGER_16; a_month, a_day: NATURAL_8) do year := a_year; month := a_month; day := a_day end feature -- Access day: NATURAL_8 -- Day of the current object month: NATURAL_8 -- Month of the current object year: INTEGER_16 -- Year of the current object feature -- Element change set_year (a_year: INTEGER_16) do year := a_year end feature {NONE} -- Constants Range_table: EL_ATTRIBUTE_BIT_RANGE_TABLE once create Result.make (Current, "[ day := 1 .. 8 month := 9 .. 16 year := 17 .. 32 ]") end end

Implicit Bit Masks

Using the concept of subranges, the class COMPACTABLE_DATE is here redefined to use subranges instead of explicit bit mask specifications. String manifests are no longer used and instead the field address operator $ is used to specify the class attribute.

class RANGE_COMPACTABLE_DATE inherit COMPACTABLE_DATE redefine Range_table end create make, make_from_compact_date feature {NONE} -- Constants Range_table: EL_ATTRIBUTE_RANGE_TABLE once create Result Result [$day] := 1 |..| 31 Result [$month] := 1 |..| 12 Result [$year] := range (-100_000, 100_000) Result.initialize (Current) end end

Class EL_ATTRIBUTE_RANGE_TABLE

Class EL_ATTRIBUTE_RANGE_TABLE is defined as a hash table with a key conforming to TYPED_POINTER [ANY]

class EL_ATTRIBUTE_RANGE_TABLE inherit EL_ATTRIBUTE_BIT_RANGE_TABLE rename make as make_masks undefine copy, default_create, is_equal redefine compact_value, make_field_arrays, set_from_compact end EL_HASH_TABLE [INTEGER_INTERVAL, TYPED_POINTER [ANY]] rename key_for_iteration as address_item, item_for_iteration as range_item export {NONE} all {ANY} valid_key, force redefine make_equal end

Bitmask Calculations

The class EL_ATTRIBUTE_RANGE_TABLE is able to calculate the smallest possible mask for each attribute by normalising the lower index to zero. The day attribute has a normalised upper value of 30 which can be stored in 5 bits. The month can be stored in 4 bits and the year range -100_000 to 100_000 requires 18 bits.

Specifiying 64-bit Ranges

The year attribute is assigned a range with the line

Result [$year] := range (-100_000, 100_000)

The purpose of the range function is to allow the specification of 64-bit ranges using INTEGER_64. Strictly speaking it is not needed for this example, and is presented for illustration purposes only.

Test Set

From class REFLECTION_TEST_SET

test_compactable_objects -- REFLECTION_TEST_SET.test_compactable_objects note testing: "[ covers/{EL_ATTRIBUTE_BIT_RANGE_TABLE}.make, covers/{EL_ATTRIBUTE_RANGE_TABLE}.initialize, covers/{EL_COMPACTABLE_REFLECTIVE}.make_by_compact, covers/{EL_COMPACTABLE_REFLECTIVE}.compact_value ]" local compact_64: NATURAL_64; date_2: RANGE_COMPACTABLE_DATE; compact_date: INTEGER date_1: COMPACTABLE_DATE; date: DATE do create date.make (2005, 12, 30) create date_1.make (2005, 12, 30) assert ("fits into 32 bits", date_1.upper_bit_index = 32) assert ("same compact", date_1.compact_date = date.ordered_compact_date) create date_2.make (2005, 12, 30) -- using range intervals to define each field assert ("fits into 27 bits", date_2.upper_bit_index = 27) compact_date := date_2.compact_date assert ("same as", compact_date = 0x031CEB7D) create date_2.make_from_compact_date (compact_date) assert ("year OK", date_2.year = 2005) assert ("month OK", date_2.month = 12) assert ("day OK", date_2.day = 30) -- Test negative year date_2.set_year (-2005) create date_2.make_from_compact_date (date_2.compact_date) assert ("year OK", date_2.year = -2005) date.set_date (2023, 11, 2) date_1.set_from_compact_date (date.ordered_compact_date) assert ("same year", date_1.year = date.year) assert ("same month", date_1.month = date.month) assert ("same day", date_1.day = date.day) end

Project to Upgrade Eiffel-Loop

If you would like to help support a project to upgrade the Eiffel-Loop libraries for the most recent GPL version of the EiffelStudio compiler there are a number of ways to do this.

  1. Share a link on social media to either of my two software products My Ching or Matryoska.
  2. Write a short review for either of the software products My Ching or Matryoska. (You can ask me for a complementary license for the full version)
  3. Purchase a license for either of the products My Ching or Matryoska
  4. Donate via Paypal