home *** CD-ROM | disk | FTP | other *** search
/ Hall of Fame / HallofFameCDROM.cdr / proglc / diff23.lzh / DIFF.DOC < prev   
Encoding:
Text File  |  1988-08-10  |  21.4 KB  |  430 lines

  1.          As a person who has to maintain large amounts of
  2.          documentation and needs to be able to track changes through
  3.          successive revisions of the same document, I quickly felt the
  4.          need for a tool which would insert change bars into
  5.          documents.
  6.           
  7.          Remembering an article in Dr. Dobb's Journal, August 1987, by
  8.          Don Krantz, I found a utility written in C which would insert
  9.          Change Bars into existing document files in normal ASCII,
  10.          exactly what I needed.  I entered the program on my system,
  11.          converting it to Turbo C (mainly using the Turbo C library
  12.          routines for string comparisons) and tested it.  Whereas the
  13.          program worked fine on small text files, I had files for a
  14.          document of 900 K text to process.  The program's limitations
  15.          became painfully obvious very rapidly.  As it was written,
  16.          the program could not:
  17.           
  18.          -    deal with differences of over 200 lines, since the
  19.               resync module only looked 200 lines ahead;
  20.           
  21.          -    handle large text files since both uppercase and normal
  22.               versions of the line were stored in memory.
  23.           
  24.          Furthermore the program was very slow on dealing with small
  25.          changes (1 line inserted or deleted) due to the way the
  26.          resynchronization algorithm was implemented.
  27.           
  28.          Rather than re-designing the program I went through a series
  29.          of revisions on the program, tackling problems in small
  30.          chunks.  Perhaps it would have been better to re-design the
  31.          program, but the resulting program has solved all of these
  32.          limitations, and works just fine for my needs.  Some of the
  33.          existing limitations are documented at the end of this
  34.          document.
  35.           
  36.          Why have I documented the change process here?  Well, I
  37.          dislike two things:
  38.           
  39.          -    reinventing the wheel;
  40.           
  41.          -    optimizing programs by delving into optimizers, or
  42.               transforming parts into assembler routines.
  43.           
  44.          I believe in optimizing programs through the use of
  45.          different, more efficient algorithms.  Changing a high level
  46.          language into assembler in selected places may give you a 10%
  47.          speed increase, or, if you are lucky, a 50% increase.  By
  48.          altering the algorithm you can achieve improvements of 100%
  49.          to 1000% much more easily (depending on the problem, of
  50.          course).  I hope that my thought processes might be of use to
  51.          someone else, which is why I document them in this document.
  52.  
  53.  
  54.                                     Page 1
  55.  
  56.  
  57.  
  58.  
  59.                            Diff - A Change Bar Tool
  60.  
  61.  
  62.          The Improvements
  63.           
  64.           
  65.          The first problem was easy to solve.  The original program
  66.          stored a line read into memory both in normal format and in
  67.          uppercase format to allow case insensitive change bars to be
  68.          inserted.  Since Turbo C provides a subroutine to compare two
  69.          strings either with case sensitivity (strcmp()) or without
  70.          (stricmp()) the memory use of the program was halved at the
  71.          cot of some processing speed (deciding which one of the two
  72.          comparison routines to use).  Since these routines have been
  73.          optimized by Borland, they actually performed faster than the
  74.          routine that was originally in the program.
  75.           
  76.          The program included a good memory management scheme, which
  77.          ensured that every line of text was only read once, and
  78.          allocated memory would be freed at the earliest possible
  79.          opportunity, when a match has been found.  Since this scheme
  80.          is quite efficient, I decided to keep it as an essential part
  81.          of the program.
  82.           
  83.          Upon inspection the program was still CPU bound, spending
  84.          most of its time in string comparisons, with the disk drive
  85.          being idle.  In order to eliminate the time on most of these
  86.          comparisons, where the same line of text is compared over and
  87.          over again with other lines, a simple hash value of the
  88.          characters in the line is added to the structure containing
  89.          the line.  This hash value, or checksum, is an exclusive or
  90.          of all the characters in the line.  Instead of comparing
  91.          lines, the program now compares the pre-calculated checksums,
  92.          and only if they are equal, will it compare the line
  93.          character for character.  This reduced the amount of
  94.          processing on similar lines considerably.
  95.           
  96.          However, the program was still very slow, which was very
  97.          noticeable on files with lots of small changes.  Furthermore,
  98.          the program couldn't cope with changes of more than 200 lines
  99.          of text.  Checking the program revealed why this was so.  The
  100.          program would always take a line from file 1, and look up to
  101.          200 lines ahead in file 2, to find the matching line.  Then
  102.          it would take line 2 of file 1, and look 200 lines ahead from
  103.          the current line of file 2 again.  And so on, until it had
  104.          found a minimum of re-sync (which is a command line
  105.          modifiable value, normally set at 5) of un-changed lines.
  106.          This meant that in order to detect a 1 line change, the
  107.          program could perform up to a 1000 line comparisons.
  108.           
  109.          Since most changes two documents are small ones, I modified
  110.          the program so that it now starts by checking only 10 lines
  111.          ahead.  If that fails, it looks 20 lines ahead, then 40, 80
  112.          and so on until it runs out of memory.  There is no upper
  113.          limit on the number of lines, other than constrained by
  114.          memory.  As a result, a small change requires far fewer
  115.          comparisons and is detected far more rapidly.
  116.  
  117.  
  118.                                     Page 2
  119.  
  120.  
  121.  
  122.  
  123.                            Diff - A Change Bar Tool
  124.  
  125.  
  126.           
  127.          Big changes can also be detected, since the program does not
  128.          have the artificial constraint of 200 lines look ahead
  129.          anymore.  If it doesn't match on 80 lines it tries 160, then
  130.          320 and so on until it runs out of memory.  Since the memory
  131.          management function is quite efficient (discarding lines as
  132.          soon as a match has been found) quite a few lines can be
  133.          stored in memory.  That is also why the compact compilation
  134.          model is used, maximizing the amount of allocatable memory
  135.          for a small program.
  136.           
  137.          There still turned out a problem with this.  The program
  138.          could now almost always synchronize, but on extremely large
  139.          changes this was still very slow.  The reason for this is,
  140.          that every time the lookahead number of lines is doubled, the
  141.          program has to start at line 1 of file 1 again, but now
  142.          checking half the number of lines in file 2 for the second
  143.          time, and half for the first time.  Ie, the same line would
  144.          be compared against the same line lots of times anyway.  In
  145.          order to minimize the amount of repeat lookups which occur
  146.          every time that the lookahead lines are increased, the
  147.          program now assumes that after 40 lines couldn't match, the
  148.          change is probably very big, and the lookahead is set to 400
  149.          lines.  If it still doesn't match, it sets them at 800, 1600
  150.          etc until it runs out of memory.
  151.           
  152.          Regrettably, although this helped a little bit, it was only a
  153.          little bit, since for each of the 400 lines in file 1 each
  154.          and everyone of the 400 lines in file 2 are checked, leaving
  155.          the program still CPU bound.
  156.           
  157.          Then a brainwave hit.  If we can use the checksum (a value
  158.          between 0 and 255) to compare lines in the files, why can't
  159.          we use it to quickly locate a line already read in for file
  160.          2?  So an array was created, with an entry for each possible
  161.          checksum value.  This entry is the start of an ordered linked
  162.          list, pointing to lines with the same checksum.  Rather than
  163.          having to check all lookahead lines in order to resync, the
  164.          program uses the checksum value of the line in file 1 to
  165.          index into the array of linked lists.  It then traverses the
  166.          list of lines with the same checksum, comparing the lines
  167.          until one has been found which is equal.  If none are found,
  168.          the program takes the next line from file 1, and repeats the
  169.          process.  If no match can be found at all, the program
  170.          restarts the lookahead procedure with twice the number of
  171.          lines to look ahead.  In order to ensure that sufficient
  172.          lines are present, lookahead lines are read every time, which
  173.          means that the memory management still works and memory gets
  174.          cleared every time a synchronization has been detected.  At
  175.          one point a resync will either be found, or end of file
  176.          reached (or memory is full -- this will not happen soon since
  177.          matched lines are discarded from memory, so the change would
  178.          have to exceed the size of memory).
  179.           
  180.  
  181.  
  182.                                     Page 3
  183.  
  184.  
  185.  
  186.  
  187.                            Diff - A Change Bar Tool
  188.  
  189.  
  190.          However, since file 2 is not scanned sequentially but
  191.          randomly, the last line before a match (which is used to be
  192.          able to produce the "deleted from file 2" difference list) is
  193.          not available any more.  In order to allow for this, the line
  194.          structure now includes a pointer to the previous line, so
  195.          that the difference summary can still be printed.
  196.           
  197.          The nextline() and discard() functions maintain the line
  198.          structure list, and have been modified to maintain the
  199.          checksum array and the previous line list as well.  Resync
  200.          has been altered substantially in order to take advantage of
  201.          the array.  Minor modifications have been made to other
  202.          routines in order to provide extra error checking.
  203.           
  204.          As a result of these modifications the program now is much
  205.          faster especially with lots of small changes (due to the
  206.          lookahead line policy), and all large changes (due to the
  207.          checksum linked list).  So much so, in fact, that it is also
  208.          I/O bound (ie the disk drive is almost operating
  209.          continuously).  And it can handle bigger changes.  The
  210.          program now can put change bars into my 900 K of document
  211.          faster than my word processor can print it.
  212.           
  213.           
  214.          Limitations of the program.
  215.           
  216.          The main limitation of the program is due to the resynchro-
  217.          nization algorithm.  The program considers a file
  218.          resynchronized if it discovers a number of matched lines.
  219.          This number is set at 3 as a default, but can be modified
  220.          using the -r option.  However, the results of this algorithm
  221.          can be surprising.  When text has been duplicated (with more
  222.          than resync lines) and sandwiched in between new text, and
  223.          the old text has been left unchanged after the inserted text
  224.          (as in the following example), a match will be detected.  In
  225.          the example assume that resync is 3.
  226.           
  227.                       file 1                        file 2
  228.           
  229.                         A                             P
  230.                         B _______                     Q
  231.                         C        \                    R
  232.                         D ______  \__________________ B
  233.                         E ____  \                     C
  234.                         F     \  \___________________ D
  235.                         G      \                      S
  236.                         H _     \                     T
  237.                         I  \     \                    B
  238.                             \     \                   C
  239.                              \     \                  D
  240.                               \     \________________ E
  241.                  A false match...                     F
  242.                                 \                     G
  243.                                  \___________________ H
  244.  
  245.  
  246.                                     Page 4
  247.  
  248.  
  249.  
  250.  
  251.                            Diff - A Change Bar Tool
  252.  
  253.  
  254.          As can be seen above, BCD in file 1 will match with the first
  255.          occurrence of BCD in file 2.  And as a result, the second BCD
  256.          in file 2 will not match with the BCD in file 1.  (The
  257.          remainder, EFGH will match again).  A more logical match
  258.          would have been to leave the PQBCDRST sequence as a new
  259.          insert, and just match on the sequence BCDEFGH.  (Note that
  260.          the first match could have been avoided by selecting the
  261.          number of lines to be resynced to be larger than 3).
  262.           
  263.          A different algorithm, called the "longest common
  264.          subsequence" algorithm, will do just that automatically.  It
  265.          will select the longest matching sequence in the file and use
  266.          that as a basis for selecting the second longest matching
  267.          sequence both above and below the longest matching sequence
  268.          just found, and so on until all sequences have been found.
  269.          This method is also particularly suitable for implementation
  270.          using hashing.  However, a disadvantage of this algorithm is
  271.          that no lines can be discarded until both files have been
  272.          processed in their entirety.  As a result either lines have
  273.          to be kept on disk (for instance storing the lseek() value in
  274.          the line buffer with the checksum, so that lines can be
  275.          retrieved from disk using direct access), or the size of the
  276.          files that can be processed is limited.  Furthermore, since
  277.          the program will recursively check for matching subsequences
  278.          (compare it for instance with CAR Hoare's QuickSort
  279.          algorithm) this algorithm will produce better difference
  280.          reports only at the cost of much more computer time.  Thus
  281.          this algorithm is used most often on mini-computer systems.
  282.           
  283.          Note that a mix between the "longest common subsequence"
  284.          algorithm as described above, and the "scan until next match"
  285.          as implemented in the program presented here can be
  286.          implemented to good effect.  Rather than having a minimum
  287.          number of lines before resync is achieved, select a maximum
  288.          number of lines after which a matching subsequence is
  289.          considered the longest, causing the file to be split.  By
  290.          setting this number as one larger than the largest sequence
  291.          which occurs more than once in a file (eg 100 lines) the
  292.          program will produce the same results as the unconstrained
  293.          version of the program, in less processing time.
  294.           
  295.          A final limitation of the program is due to the fact that it
  296.          compares files on a line by line basis.  This is fine for for
  297.          instance program source code, but gives problems with most
  298.          automatic paragraph reformatting features available in word
  299.          processors.  The result of inserting a word in one sentence
  300.          can often be a re-format of several lines of text in the
  301.          paragraph.  Although in reality those sentences have not
  302.          changed, the program will think they are changed.
  303.           
  304.          The solution is to perform the comparison on a sentence by
  305.          sentence (or word by word) basis rather than line by line.
  306.          The difficulty is however not in the synchronization
  307.          algorithm (since that can stay as it is), but in the
  308.  
  309.  
  310.                                     Page 5
  311.  
  312.  
  313.  
  314.  
  315.                            Diff - A Change Bar Tool
  316.  
  317.  
  318.          algorithm which inserts change bars on the correct line in
  319.          the correct place in the document.  You would have to store a
  320.          representation of the original line of text somewhere, and
  321.          use a pointer to associate every word with the line from
  322.          which it came.  This modification is left as an exercise for
  323.          the reader, however.  If I ever get to it, I'll put the
  324.          resulting code in Public Domain again.
  325.           
  326.           
  327.          Using the diff program.
  328.           
  329.          diff - text file differencer and change barrer
  330.          usage: diff [option{option}] newfile oldfile [barfile]
  331.           
  332.          Where the options are optional and can be inserted in any
  333.          order except for the -n and -o option (which must appear in
  334.          that order).  Newfile and Oldfile are required (you don't
  335.          want to compare a file against nothing, do you?).  Barfile is
  336.          optional, but required if you want a file with change bars in
  337.          it as output.
  338.           
  339.          The options are:
  340.           
  341.            -t   trace operation, default off
  342.            -b n column of barfile for change bar, default 78
  343.            -h n lines at top of page to skip for headers, default 0
  344.            -f n lines at bottom of page to skip for footers,
  345.                 default = 0
  346.            -p n lines per page (embedded form feeds override),
  347.                 default = 66
  348.            -c   uppercase/lowercase is significant (default is off)
  349.            -r n lines that must match before files are considered
  350.                 synced after differences are found. default = 3
  351.            -w   blank lines are considered significant (default is
  352.                 not)
  353.            -s   screen listing off (default is on)
  354.            -n n pages in NEWFILE to skip before compare.  Also sets -
  355.                 o. default = 0
  356.            -o n pages in OLDFILE to skip before compare.  Must come
  357.                 after -n.  default = 0
  358.           
  359.           
  360.          The Trace option (-t) only shows if the program has been
  361.          compiled with the debug option on.  It shows debug messages.
  362.           
  363.          The Bar-column option (-b) selects the column in which the
  364.          vertical bar will be placed.  The default is column 78.
  365.          Column 0 will put it in the left most position on the page.
  366.          If your program contains embedded tabs, these are not
  367.          expanded automatically to ensure that when the bar should
  368.          appear in column 78, it actually does appear there.  So use
  369.          the option -b 0 in this case.
  370.           
  371.           
  372.  
  373.  
  374.                                     Page 6
  375.  
  376.  
  377.  
  378.  
  379.                            Diff - A Change Bar Tool
  380.  
  381.  
  382.          The Header option (-h) and Footer option (-f) allow you to
  383.          specify the number of lines to skip at the top of the page
  384.          and the bottom of the page for headers and footers.  You
  385.          don't usually want change bars just because the date or page
  386.          numbering of a file has changed.
  387.           
  388.          The Page length option (-p) allows you to specify the number
  389.          of lines per page.  66 is the Default.
  390.          The Case Sensitive (-c) option will insert change bars if
  391.          just the case of text has changed.  The default is no case
  392.          sensitivity, so that for instance changing "New york" into
  393.          "New York" does not generate a change bar.  For case
  394.          sensitive language's program listings you'll want to use the
  395.          -c option.
  396.           
  397.          The Resync option (-r) specifies the number of lines that
  398.          must match before the files are considered re-synced.  The
  399.          limitations of this parameter have been discussed above.  The
  400.          default is 3.  For program source code you'll want to
  401.          experiment with slightly larger values.
  402.           
  403.          The White option (-w) makes blank lines significant.  The
  404.          default is that they are not significant.  I never use this
  405.          option, but perhaps you have a need for it.
  406.           
  407.          The Screen option (-s) turns off the screen listing of the
  408.          differences.  This speeds up the program when you are not
  409.          interested in the differences list, or when you use the
  410.          program in a batch file.  Note that the program will give an
  411.          error message if you specify the -s option and no bar file
  412.          (since then all it would do is waste computer time).  The
  413.          differences list to screen has the following format:
  414.           
  415.          002:12<This text has been added to the newfile on page 2,
  416.          002:13<lines 12 and 13.
  417.          002:12>This line has been deleted from oldfile, page 2 and
  418.          002:13>lines 12 and 13 and 14.  Note that every time a match
  419.          002:14>has been found a blank line is printed first.
  420.           
  421.          (Note the direction of the arrows: < for insertion into the
  422.          newfile, >for deletion (extraction if you like) from the
  423.          oldfile to the newfile).
  424.           
  425.          The Newfile skip option (-n) and the Oldfile skip option (-o)
  426.          allow you to skip pages at the beginning of the file for
  427.          header pages and contents lists and the like, where you are
  428.          not interested in change bars.  Note that the -n option sets
  429.          the -o option (unless you override the -o s cnn    eFh ri-----F :        [Skip]
  430.