Skip to contentSkip to conversation

Painlessly shrinking kernel messages (Re: kernel support for non-english user messages)

DISCUSSION Timothy Miller 3 participants 6 messages Apr 10, 2003 – Apr 11, 2003

lore
Share & tools
lore
#0 lore raw

I took the liberty of reading the FAQ (yeah, I saw 9.16) and joining the list after reading an interesting recent discussion on i18n of kernel messages. In short, the primary maintainers of the kernel don't want it, and I agree with them.

HOWEVER, the discussion inspired me to think about ways of reducing some of the unfortunate but necessary bloat caused by keeping all of those strings in RAM. Naturally, any way to do this must be absolutely painless, so I came up with the following set of restrictions:

- Absolutely no requirement to change existing strings, unless you feel like it
- Must be easy to use
- Must actually shrink the kernel
- The impact on the way kernel messages appear should be minimized

To be brief, the idea I came up with was to identify the 128 most common words in kernel messages and replace them with single character values above 127 which printk would decode on the way out. Once the list was determined, there would be a header file people could use, at their leisure, to make stubstitutions. So, for instance, instead of having this:

   printk("invalid: ...");

We would have this:

   #define MSG_INVALID "\200"
   ...
   prink(MSG_INVALID "...");

To judge the practicality of this, I used 'strings' on an uncompressed kernel image (2.4.20, IIRC) and then ran it through this:

tr '[:lower:]' '[:upper:]' | tr '[:blank:]' '\n' | sort | uniq -c | tr ' ' 0

This gave me a list of all words found in the kernel along with their counts. Then I ran it through a positively awful little C program which I wrote to determine not the 128 most frequent, but rather, the 128 that would result in the maximum shrinkage (maximize count * (length-1)). The results of that run are given below. The results of the test are that this approach might save up to 62424 bytes of kernel space which is only about 3% of the kernel image size I got the strings from, but it's nearly 27% of the total output I got from 'strings'. Is it worth it? Maybe not yet, but then again, there may be an even more intelligent approach to this compression that we could use, hopefully one which wouldn't require any more effort to use.

Here's are the results:

count string
-------- --------

     37 GIGABIT
    102 BLOCK
     62 NULL
    871 [^_]
     26 INTERFACE
     23 MICROSYSTEMS
     75 RAGE
    338 SE
    226 TECH
    113 DEVICE
    214 <3>
    838 PC
     19 <3>INIT_MODULE:
     35 REGISTER
     41 <3>EXT3-FS
    656 UWVS
     57 NETWORK
     32 SUPPORT
     97 COMPUTER
    878 [^_
    137 NET
    198 MODE
    534 INC
     33 INTERNATIONAL
     59 CARDBUS
    203 TECHNO
    119 TECHNOLOGY
     46 CORP.
     31 EXT2-FS
    290 CONTROLLER
     64 ASSERTION
     83 DATA/FAX
    249 DATA
     60 KERNEL:
    304 CONTROL
     33 INVALID
    322 %D
    486 PCI
    185 INC.
     61 ERROR
     80 PORT
    154 IDE
     74 INODE
    102 <4>
     88 KERNEL
     52 ELECTRONICS
     44 <3>EXT3
    117 FAILED
     70 AUDIO
     83 HOST
     27 SEMICONDUCTOR
     50 CHIPS
     63 DEVFS
    117 ETHERNET
    299 ID
    291 COM
     46 CANNOT
     24 TRANSACTION
    238 TO
     79 TECHNOLOGIES
     63 %08X
     98 D$$
     37 PROCESS
    288 CORP
     56 DATA/FAX/VOICE
     39 COMMUNICATIONS
     44 10/100
     38 SERIAL
    146 CORPORATION
    236 TEC
    107 MICRO
     26 MICROSYSTEM
     95 ADAPTER
    324 NO
     50 POWER
    121 56K
     27 ACCELERATOR
     33 RESEARCH
     21 INTEGRATED
    271 PRO
     19 TECHNOLOGIES,
    237 LT
     43 CHIPSET
     28 NETWORKS
    317 L$
     40 <3>EXT3-FS:
   1665 CO
    192 BRIDGE
     13 MICROELECTRONICS
    157 JOURNAL
    147 FOR
     91 9D$
     18 CYBERSERIAL
     54 CYBER
     56 MEMORY
     34 DATA/FAX/VOICE/SPKP
     49 SMART
    207 LTD
    137 TCP
     57 CACHE
    407 T$
    160 <6>
     26 GRAPHICS
    888 D$
    140 SYSTEMS
    249 AT
      6 JOURNAL->J_COMMITTING_TRANSACTION
    142 MODEM
     32 CHANNEL
    131 %S:
    394 %S
     14 COMMIT_TRANSACTION
     63 FILE
     28 SMARTDAA)
     67 CHIP
     30 WINMODEM
    113 NOT
    139 ETH
    331 DEV
    197 FO
     52 VIDEO
     73 ELECTRONIC
     67 EXT3
     99 CARD
   1336 IN
    222 SYSTEM
    197 AD
     53 COMMUNICATION

Total reduction: 62424

Comments?

NOTE: I realize that some of those words probably aren't actually "strings" in the kernel. This is a feasibility test, not a suggested list.

#1 lore raw
Quoted 5 lines · Timothy Miller

To be brief, the idea I came up with was to identify the 128 most common words in kernel messages and replace them with single character values above 127 which printk would decode on the way out. Once the list was determined, there would be a header file people could use, at their leisure, to make stubstitutions. So, for instance, instead of having this:

Not a totally crazy idea. You could also do 5pack and some of the other string tricks people have used in time. You also dont need to do word boundaries.

For embedded at least this is far from ludicrous as a concept. The tricky piece for all of these is working out how to grab each printk format string and do things to it. That lets you do compression, removal, internationalisation, cataloguing ..

#2 lore raw
Quoted 4 lines · Alan Cox

Not a totally crazy idea. You could also do 5pack and some of the other string tricks people have used in time. You also dont need to do word boundaries.

My google search for '5pack' didn't come up with anything relevant. Things that come to mind include converting to a character set which requires fewer than 8 bits per character and then packing them into bytes. Or perhaps making a list of every quintuplet of characters that ever occurs and assign them codes.

I initially considered the idea of ignoring word boundaries. I rejected it because part of the "painless" factor would be that it could be done manually without a lot of thinking. But I will run a test which ignores word boundaries and see what kinds of results I get. Of course, if we want to do something that involves some post-compile magic or whatnot, then we can do all sorts of gnarley tricks. But that doesn't differ (in complexity) much from the idea someone else mentioned which was to completely remove all messages from the kernel by magically converting them to numbers or hashes and then decoding them outside of the kernel.

There was mentioned a valid point that boot messages need to be handled properly by the kernel before any services are up. Separating the boot messages from the non-boot messages would require manual intervention that goes against the painless factor, and is the pie slice containing only non-boot messages large enough that it's worth it? There seem to be quite a lot of boot messages that could benefit from some sort of completely-in-kernel compression.

Quoted 7 lines

For embedded at least this is far from ludicrous as a concept. The tricky piece for all of these is working out how to grab each printk format string and do things to it. That lets you do compression, removal, internationalisation, cataloguing ..

Hmmm...
- Make gcc produce assember output
- Find all calls to prink
- Cross-reference those against all static strings
- Compress the strings
- Run through gas, etc.

The problem with this approach is that we have to deal with different architectures. The plus is that any unsupported arch just doesn't run the compression tool and uses regular printk.

How about:
- Use perl or yacc or something to parse the kernel source for strings
- Compress them
- Make the substitutions inline in the source as part of the pre-processing stage
- Compile

Heck, we could just embed this functionality directly into the preprocessor. Unfortunately, this one is somewhat beyond my current knowledge of the tools that would make it convenient.

Just as a note, I worked on my test program to make it a more accurate. For 128 codes, the actual reduction is 38946 bytes. For this algorithm, I look to see if any of the shorter words are contained in any of the larger ones; in the case where the shorter word's substitution would shrink the kernel more than the larger, I add the larger word's count to the smaller and delete the larger.

If we were to outlaw some of the lower characters, such as most non-printing characters and all lower-case, then that brings us up to having 184 codes to work with. That lets us save 42692 bytes. If we were to go to two-character codes, where the first one is 128-255 and the second is 1-255, that brings the number of codes up to 32640. It turns out that, with my current algorithm, it doesn't buy anything, and it also violates the painless factor by giving people a huge list of words they have to pick from when writing kernel messages. Also, it turns out that there are only just over 500 different words which would save more than 2 bytes by being encoded.

I need to get a LOT more clever about this before it's worth doing. I'll try the no-word-boundaries approach. And we'll see how interested other people are in having to DEAL with it.

BTW, should I faint or something because THE Alan Cox responded to my first post to lkml? :) You hate it when people say that sort of thing, don't you. :)

#3 lore raw
Quoted 2 lines · Timothy Miller

My google search for '5pack' didn't come up with anything relevant.
Things that come to mind include converting to a character set which

Its a thing from the old 8bit gaming world. You code in 5bit chunks with a leading length marker. 5bits is enough for a-z and some bits of punctuation, plus capital implying space and 'escape' for an 8bit sequence block.

Gets you a bit under 40% compression with real life data and takes about 200 bytes to decode

#4 lore raw
In reply to Alan Cox

This time, my text compression program ignores word boundaries. Below are the results.

"shrink" is the size reduction due to replacing the given string with a one-byte code. (accounts for space required for codes and the decode string plus nul). "count" is how many times the string appears.
Strings are delimited by [brackets].

At the end, the "original size" accounts for null termination for all strings. The "new size" is the size of the text with the codes inserted plus the amount of space required to store what the codes map to.

Nothing accounts for the space wasted due to the strings being 32-bit aligned or whatever various architectures might require. This is just an estimate.

What the heck is "[^_]" ? Is it a machine instruction that 'strings' is mistaking for a string? I grep'd through the kernel source and didn't find it.

The results are that the kernel messages are reduced from 232690 to 154365, which is a savings of 33%. Not bad, but it's probably still not worth it yet; the pain is still greater than the benefit.

Here's the list.

shrink count word
------ ----- ------------------------------

3166   1057  [[^_]]
2888    290  [ CONTROLLER]
1963    656  [UWVS]
1771    198  [ TECHNOLOG]
1758    881  [ IN]
1678    561  [TION]
1438    721  [ CO]
1268    255  [SYSTEM]
1126    189  [ BRIDGE]
1116    560  [PCI]
994    167  [JOURNAL]
986     53  [F 56K DATA/FAX/VOICE]
964    323  [    ]
932    468  [TER]
870    110  [ ETHERNET]
824    414  [ING]
772    388  [DEV]
745     84  [ELECTRONIC]
742    249  [MODE]
738    371  [POR]
706    355  [ED ]
706     60  [KERNEL: ASSER]
643    216  [LOCK]
592    298  [PRO]
586    295  [ENT]
554    279  [S: ]
516    260  [REA]
510    257  [IDE]
496    167  [ABLE]
478     81  [TRANSAC]
474    239  [AGE]
474    239  [NET]
466    235  [ON ]
464    234  [EXT]
458    116  [CACHE]
424    214  [<3>]
418    106  [REGIS]
416    210  [RES]
415    140  [ LTD]
415    140  [FAIL]
414    209  [ %D]
409    138  [DATA]
394    199  [COM]
394    133  [NOT ]
390    197  [ARD]
388    196  [E_R]
386     98  [ ADAP]
378    191  [FOR]
374     95  [MICRO]
373     16  [ ROAD RUNNER FRAME GRABBER]
370    187  [SET]
354    179  [VER]
350    177  [ELE]
348    176  [ALL]
326    165  [100]
318    161  [AND]
312    158  [_IN]
307    104  [TIME]
306    155  [<6>]
303     62  [AIC-78]
298     26  [SEMICONDUCTOR]
294    149  [SER]
288    146  [AL ]
288     59  [MEMORY]
283     96  [%02X]
282    143  [LIN]
280    142  [ATE]
273     56  [BUFFER]
272    138  [TCP]
270    137  [ %S]
262     67  [AUDIO]
258     66  [82801]
256    130  [ECT]
254     65  [ HOST]
254    129  [PER]
253     16  [08X %08X %08X %08X]
250    127  [TO ]
248    126  [OUN]
248    126  [PHT]
246     63  [C(%D)]
246     32  [SMARTDAA)]
244     42  [CANNOT ]
244    124  [ORT]
238    121  [9D$]
238    121  [CHI]
238     49  [F 56K ]
238     21  [FIBRE CHANNEL]
236    120  [T_R]
234    119  [BIT]
233     48  [MODULE]
232    118  [CON]
232    118  [NIC]
230    117  [%LU]
230     59  [POWER]
230    117  [REE]
226     58  [ERROR]
215     32  [%U.%U.%U]
215     32  [GRAPHICS]
214    109  [EST]
214     73  [NULL]
214     73  [SOCK]
212    108  [ALI]
212    108  [TRI]
210     54  [WRITE]
208    106  [%08]
202    103  [BUS]
202     69  [GDT ]
202     52  [GROUP]
200    102  [<4>]
200    102  [D$ ]
200    102  [OUT]
196     67  [FILE]
196     67  [GET_]
193     16  [ MIL-STD-1553 ]
190     25  [ [APOLLO ]
184     94  [ESS]
182     47  [CHECK]
182     93  [D$$]
176     90  [STA]
174     89  [AGP]
174     45  [CYBER]
173      8  [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
169     58  [TECH]
168     86  [ RE]
166     85  [82C]
160     82  [000]
160     82  [ACC]
160     82  [END]

original size=232690, new size=154365

You may notice a goof my program makes. The string "%U.%U.%U" appears in there, when what is actually found in the kernel is "%U.%U.%U.%U". That's because there are more of the former, but only because the program didn't realize that pairs of the former overlap with each other. I'll see about fixing that later, but really, I think we'll need to hand-pick strings anyhow.

Lastly, I decided to allow two-character strings to be encoded and upped the number of codes to 184. This is the result:
original size=232690, new size=109133
That's a reduction of 53%.

#5 lore raw

this is definantly something that wouldn't make sense to do manually, but if somone can figure out how to do this as part of the build process the 80K saved can't hurt.

David Lang

Quoted 188 lines · Timothy Miller

Date: Fri, 11 Apr 2003 19:02:33 -0400
From: Timothy Miller <[email protected]>
To: [email protected]
Subject: Re: Painlessly shrinking kernel messages (Re: kernel support for
non-english user messages)

This time, my text compression program ignores word boundaries. Below are the results.

"shrink" is the size reduction due to replacing the given string with a one-byte code. (accounts for space required for codes and the decode string plus nul).
"count" is how many times the string appears.
Strings are delimited by [brackets].

At the end, the "original size" accounts for null termination for all strings.
The "new size" is the size of the text with the codes inserted plus the amount of space required to store what the codes map to.

Nothing accounts for the space wasted due to the strings being 32-bit aligned or whatever various architectures might require. This is just an estimate.

What the heck is "[^_]" ? Is it a machine instruction that 'strings' is mistaking for a string? I grep'd through the kernel source and didn't find it.

The results are that the kernel messages are reduced from 232690 to 154365, which is a savings of 33%. Not bad, but it's probably still not worth it yet; the pain is still greater than the benefit.

Here's the list.

shrink count word
------ ----- ------------------------------

3166   1057  [[^_]]
2888    290  [ CONTROLLER]
1963    656  [UWVS]
1771    198  [ TECHNOLOG]
1758    881  [ IN]
1678    561  [TION]
1438    721  [ CO]
1268    255  [SYSTEM]
1126    189  [ BRIDGE]
1116    560  [PCI]
 994    167  [JOURNAL]
 986     53  [F 56K DATA/FAX/VOICE]
 964    323  [    ]
 932    468  [TER]
 870    110  [ ETHERNET]
 824    414  [ING]
 772    388  [DEV]
 745     84  [ELECTRONIC]
 742    249  [MODE]
 738    371  [POR]
 706    355  [ED ]
 706     60  [KERNEL: ASSER]
 643    216  [LOCK]
 592    298  [PRO]
 586    295  [ENT]
 554    279  [S: ]
 516    260  [REA]
 510    257  [IDE]
 496    167  [ABLE]
 478     81  [TRANSAC]
 474    239  [AGE]
 474    239  [NET]
 466    235  [ON ]
 464    234  [EXT]
 458    116  [CACHE]
 424    214  [<3>]
 418    106  [REGIS]
 416    210  [RES]
 415    140  [ LTD]
 415    140  [FAIL]
 414    209  [ %D]
 409    138  [DATA]
 394    199  [COM]
 394    133  [NOT ]
 390    197  [ARD]
 388    196  [E_R]
 386     98  [ ADAP]
 378    191  [FOR]
 374     95  [MICRO]
 373     16  [ ROAD RUNNER FRAME GRABBER]
 370    187  [SET]
 354    179  [VER]
 350    177  [ELE]
 348    176  [ALL]
 326    165  [100]
 318    161  [AND]
 312    158  [_IN]
 307    104  [TIME]
 306    155  [<6>]
 303     62  [AIC-78]
 298     26  [SEMICONDUCTOR]
 294    149  [SER]
 288    146  [AL ]
 288     59  [MEMORY]
 283     96  [%02X]
 282    143  [LIN]
 280    142  [ATE]
 273     56  [BUFFER]
 272    138  [TCP]
 270    137  [ %S]
 262     67  [AUDIO]
 258     66  [82801]
 256    130  [ECT]
 254     65  [ HOST]
 254    129  [PER]
 253     16  [08X %08X %08X %08X]
 250    127  [TO ]
 248    126  [OUN]
 248    126  [PHT]
 246     63  [C(%D)]
 246     32  [SMARTDAA)]
 244     42  [CANNOT ]
 244    124  [ORT]
 238    121  [9D$]
 238    121  [CHI]
 238     49  [F 56K ]
 238     21  [FIBRE CHANNEL]
 236    120  [T_R]
 234    119  [BIT]
 233     48  [MODULE]
 232    118  [CON]
 232    118  [NIC]
 230    117  [%LU]
 230     59  [POWER]
 230    117  [REE]
 226     58  [ERROR]
 215     32  [%U.%U.%U]
 215     32  [GRAPHICS]
 214    109  [EST]
 214     73  [NULL]
 214     73  [SOCK]
 212    108  [ALI]
 212    108  [TRI]
 210     54  [WRITE]
 208    106  [%08]
 202    103  [BUS]
 202     69  [GDT ]
 202     52  [GROUP]
 200    102  [<4>]
 200    102  [D$ ]
 200    102  [OUT]
 196     67  [FILE]
 196     67  [GET_]
 193     16  [ MIL-STD-1553 ]
 190     25  [ [APOLLO ]
 184     94  [ESS]
 182     47  [CHECK]
 182     93  [D$$]
 176     90  [STA]
 174     89  [AGP]
 174     45  [CYBER]
 173      8  [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
 169     58  [TECH]
 168     86  [ RE]
 166     85  [82C]
 160     82  [000]
 160     82  [ACC]
 160     82  [END]

original size=232690, new size=154365

You may notice a goof my program makes. The string "%U.%U.%U" appears in there, when what is actually found in the kernel is "%U.%U.%U.%U".
That's because there are more of the former, but only because the
program didn't realize that pairs of the former overlap with each other.
I'll see about fixing that later, but really, I think we'll need to
hand-pick strings anyhow.

Lastly, I decided to allow two-character strings to be encoded and upped the number of codes to 184. This is the result:
original size=232690, new size=109133
That's a reduction of 53%.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/