home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 June / SIMTEL_0692.cdr / msdos / txtutl / diff23.arc / DIFF.DOC < prev    next >
Encoding:
Text File  |  1988-08-03  |  21.7 KB  |  463 lines

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