RE: [code-review] list scope
"Hodges, Paul" <Paul.Hodges-zv7RHi0Am8a1Z/[email protected]> Fri, 12 Sep 2003 09:28:51 -0500
| Newsgroups | gmane.comp.lang.perl.code-review-ladder |
|---|---|
| Message-ID | <9C375DDD9B669243A2D78FCD607E894003E4FC4B@bremo-jg> |
> -----Original Message----- > From: [email protected] [mailto:[email protected]] > Sent: Friday, September 12, 2003 4:17 AM > To: Hodges, Paul; code-review-ladder-wool9L35kiek0rBIEb6pKdBc4/[email protected] > Subject: RE: [code-review] list scope Thanks for the response. :) > I would have thought that showing any code would be fine. > Everyone still learns from the analysis even if they never have a use for the code. That was my thinking, but it seemed worth asking before putting deep dark tangles of pre-mangled code up on the list. I'll start with subsections in the meantime. > I've run into something similar to your problem before. > If you end up running various tweaked analyses on the same > big, hard to parse files you may get great benefit from > preparsing them into a faster to parse format. > You could use Storable for instance but for maximum speed I > think you won't beat pack/unpack. I tried several versions, but unpack seemed the best option in a pure Perl solution. I think the real slowdown is the process of putting it on the object, though: sub parse { no warnings; # to quiet > & == on 0A's (special account types) (*self,$rec) = @_; # these are our() my $rt = substr($rec||=$self{rec},0,2) or confess "invalid usage"; if ($rt > 1) { # all greater than 01 is usage, most common type @self{@usg_fields} = unpack $usg_layout, $rec; $self{usg} = $rec; # save the usage level raw record } elsif($rt == 1) { # 01 records are line data, next most common @self{@linefields} = unpack $linelayout, $rec; @self{@usg} = (); # line cleans out previous usage $self{line} = $rec; # save the line level raw record } else { # 0[0A] is account data, only other types @self{@acctfields} = unpack $acctlayout, $rec; @self{@clean} = (); # account cleans out line & usage data $self{acct} = $rec; # save account level raw record } # should return $rec (maybe better to explicitly return $self?) } @usg_fields, @linefields, @acctfields, @usg, and @clean predefined for readability and hopefully a little more speed. I'm not sure of the details on this, but if that isn't faster, I'm assuming it doesn't matter. If it does, I probably just need to go back to the C module version and tweak there anyway. > It's a bit of work to split your process into a parse and dump phase, > followed by an undump and analyse phase but if you end up reanalysing even > once, it sounds like you'll get that time back pretty quickly. Boss doesn't want us rewriting the file format -- we have over a decade of legacy code reading the current format that *isn't* going to be rewritten to a new one, so I have to say I agree with that one. :/ Accordingly, we're stuck with the file format as-is. The files are around a GB each, and there are 12, and we have to store over a year's worth on limited disk space, so we keep them gzip'd. We open gzcat pipes in our programs to decompress on the fly as an internal input stream, and usually output to compression pipes as well, so the data never touches the disk unzipped. All account data is stored on an account record, and all data following until the next account record is associated with that account, but there's nothing on the subsequent records to say so except sequence, so we can't do any sorts on the files. There are several subcategories as well. It's a structure that begs for an object to store less granular data, and to automate cleaning that storage out at the next account. > Of course improving the parsing sounds like it would be worth doing too. > In that same project I made good gains also by switching some method > calls inside tight loops to be function calls. Of course this is not > something to do without careful thought but I had some methods that were > never called by anything outside their own class and so I just turned them > into functions. Done that where I could, but the main problem is that I need to be able to tell the method I want so-and-so to be the current record now, which is usually going to be by saying "here, parse this one". > Finally, putting loops _inside_ methods rather than around > them can be a big help. So convert from > > foreach my $line (@lines) { > push(@parsed, $parser->parse_line($line)); > } > > to > > push(@parsed, $parser->parse_lines(@lines)); > > and if you need it you can just reimplement parse_line as a > wrapper around parse_lines. Absolutely -- done that, too. :) > If @lines is enormous or is coming in from a file then split them into > batches and you'll still see a speed up. > Obviously there's not much benefit if parse_lines needs 5 > minutes per line, > F I switched from reading one record on one method call and pasing on a subcall to reading a MB and splitting it into an array or records on one call, and having each request for another record just advance through that array (reloading on the fly at need), but I still have to parse each record onto the object. I did all that before posting. Nothing seems to really help enough to matter. :( sub blockread { my ($self,$buf) = shift; my $read = read($self->{_fh},$buf,1105920) or return; # 54 bytes recs push @{ $self->{_recBlockBuffer} }, split /\r\n/, $buf; # CRLF terminated $read; } sub nextrec { *self = $_[0]; *block = $self{_recBlockBuffer}; my $ndx = $self{_block_ndx}++; if ($ndx > $#block) { @block = (); # clean out the old $_[0]->blockread; # read in the new $ndx = 0; # point at the fresh data } $rec = $self{rec} = $block[$ndx]; } I wanted to make that block assignment in blockread() be $self->{_recBlockBuffer} = [ split /\r\n/, $buf ]; but there is almost certainly a few records left over from any attempt to read an account at a time using sub readacct { *self = $_[0]; *block = $self{_recBlockBuffer} ||= [ ]; my $ndx = $self{_block_ndx} ||= 0; SCAN:{ $ndx++ until not $block[$ndx] or $ndx and $block[$ndx] =~ /^0[0A]/; unless ($block[$ndx]) { last SCAN unless $_[0]->blockread; # read new, bail if eof redo SCAN; # continue until we have the whole account } } $self{_recBlockBuffer} = [ @block[$ndx..$#block] ]; # block sans acct $self{_block_ndx} = 0; # reset to top [ @block[0..$ndx-1] ]; # return accountref } readacct() isn't something I expect to see used often, but it's been needed before.... ***** "The information transmitted is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers."