简体   繁体   中英

To Save a loop or to go with 2 methods - Convention vs. Performance

So everyone who cares about best practices in OOP and keeping code clean and in OOP knows that methods shouldn't be doing more than one thing. Methods are discrete units that do one thing and get out.

But here's a situation though where you could save some processing and improve performance if you were to combine 2 methods which are really doing 2 things into one and reuse the existing for loop that you already have in the first method:

private void RemoveDiscontinuedItems()
{
    for(int s = 0; s < itemList.Count; s++)
    {
        if(!itemList[s].ItemIsOnSite)
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }              
    }
}

private void RemovePriceChangedItems()
{
    for (int s = 0; s < itemList.Count; s++)
    {
        if(!PricingOptionIsValid(itemList[s]))
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
    }
}

These are called at page load. One removes items that are discontinued. The other removes items that have some pricing options that have changed and removes them from the same list.

Now if we were to stick with best practices, one could say that these are 2 completely independent purposes, thus we should not combine the logic in both these methods. That would then make the method be doing 2 things and I'd also have to come up with some f'd up name like RemoveDiscontinuedAndPriceChangeItems() or a generic name that doesn't tell me jack sh** like RemoveInvalidItemsFromList():

private void RemoveDiscontinuedItems()
{
    for(int s = 0; s < itemsList.Count; s++)
    {
        if((!itemList[s].ItemIsOnSite))
        {
            RemoveItem(orderItemsSavedList[s].Id);  // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
        else if(!PricingOptionIsValid(itemList[s]))
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
    }

however thinking about the performance side, calling 2 methods that are looping through the same list to remove some items, would be more costly in cycles.

So, anyone against combining or are you for combining in this situation? Would like to hear some opinions out there.

Why not refactor so that each method performs a single action, rather then doing the loop. Then in the body of the loop call each method as needed.

Update Here is a quick example based on your methods. Obviously the Loop method would be something different, in you application, however, I didn't have any other context for what you were doing. Also, I changed your for loop to a foreach.

private void Loop()
{ 
    foreach (Item i in itemList)
    {
         if(!item.ItemIsOnSite)
         {
            RemoveDiscontinuedItems(i)
         }
         if(!item.PricingOptionIsValid)
         {
            RemovePriceChangedItems(i)
         }

    }
}

private void RemoveDiscontinuedItems(itemType item)
{

    RemoveItem(item.Id); // remove it from the DB
    item.Remove; // remove it from the collection              

}

private void RemovePriceChangedItems(itemType item)
{
    RemoveItem(item.Id); // remove it from the DB
    item.Remove; // remove it from the collection

}

I think you're factoring is in the wrong place.

If you wrote it like this:

public void ConditionallyRemoveItems(Func<Item,bool> predicate)
{
    for (int s=0; s < itemsList.Count; s++) {
        if (predicate(itemList[s])) {
            RemoveItem(orderItemsSavedList[s].Id);
            itemList.RemoveAt(s);
            s--;
        }
    }
 }

 // ...
 ConditionallyRemoveItems(i => !i.ItemIsOnSize || !PricingOptionIsValid(i));

I also don't really like your style of messing with the loop variable - I prefer this style:

List<Item> itemsToRemove = new List<Items>();
foreach (Item i in itemList) {
    if (predicate(i)) {
        itemsToRemove.Add(i);
    }
}
foreach (Item i in itemsToRemove)
    itemList.Remove(i);

and if you don't like the performance of that, you can always do this:

List<Item> itemsToKeep = new List<Items>();
foreach (Item i in itemList) {
    if (!predicate(i)) {
        itemsToKeep.Add(i);
    }
 }
 itemList = itemsToKeep;

A few things:

  • I don't see why you are looping forward when removing the items. You should be looping backwards and avoid the messy index manipulation when you perform a removal.

  • Thinking of this in terms of removing one item vs another based on an attribute is incorrect. You should see it as filtering the list. To that end, you should have a method that takes a Predicate<T> and then returns an IEnumerable<T> which you can then enumerate though (or an IList<T> , the same, if you want to just mutate the list). This way, you have the operation of filtering the list and the conditions separate (which is better separation IMO).

  • If you have access to LINQ, then there really is no reason to do this. You should be using a where filter on the original list, and if you want, factoring out the filtering logic into separate methods which will take the item and see if it should be returned. You can then construct your where clause (or Predicate<T> to pass to the Where extension method) from that.

this is probably the simplest way to do it, using a loop :

//counting backwards is easier when you are doing removals from a list
for( int i = lst.Count -1; i>= 0; i--)
{
    if(condition1 || condition2)
    {
         RemoveFromDB(lst[i]);
         lst.RemoveAt(i); 
    }
}

you can refactor that to use the functional methods provided by the framework:

var toRemove = lst.FindAll( 
        item => !PricingOptionIsValid(item) || !item.ItemIsOnSite() 
       );
toRemove.ForEach( item => 
        {
            RemoveFromDB(item.ID);
            lst.Remove(item);
        });

and you could write this without the toRemove variable, by chaining the ForEach onto the FindAll

There are several good suggestions on how to simplify and clarify your current code.

When considering performance always start with clean and concise code, don't worry about optimization until you have PROVEN a need to optimize.

Almost everything anyone writes in a high-level language:
a) COULD be faster.

b) is fast ENOUGH.

Seems like the standard approach if possible would be to make a function out of the loop contents, and a function that does both things:

doBothThings()
{
    for(sharedLoop)
    {
        function1(loop counter);
        function2(loop counter);
    }
}

Gives you the performance but still separates the two pieces of functionality into separate functions. Obviously, not so simple if the two functions involve code before/after the loop.

In C# I would almost certainly not repeat the removal logic at all. Consider something like:

    private delegate bool RemovalExpression(ItemType item);
    private void RemoveItems(RemovalExpression shouldRemove)
    {
        for (int s = 0; s < itemList.Count; s++)
        {
            if (shouldRemove(itemList[s]))
            {
                RemoveItem(itemList[s].Id);
                itemList.RemoveAt(s);

                s--;
            }
        }
    }

Which can be naturally used as:

RemoveItems(item => !item.ItemIsOnSite);
RemoveItems(item => !PricingOptionIsValid(item));
RemoveItems(item => (!item.ItemIsOnSite || !PricingOptionIsValid(item)));

etc.

Also from the sounds of it, you shouldn't be worrying about looping micro-optimizations at this point. If you don't have data that explicitly indicates that you're spending a disproportionate amount of your time in item removal, you have no way of knowing which construct will be "faster", nor any objective way of knowing whether your choice was worth the time investment, or a needless complication, or an outright pessimization of performance.

Therefore, write for maintainability; simple and clear and with no repetition of logic.

Since both of your methods are removing items from your list, it doesn't necessarily make sense to combine the loops. You should determine if there's a significant performance difference between the two methods that you're using.

For example, if PricingOptionIsValid is an expensive operation (hits the database or whatever), you would want to call that in a second loop, after you've pruned as many items as possible with the first loop.

If the order of the tests doesn't matter, you should place the more likely branch first, and the less likely branch second. If ItemIsOnSite is false only 1% of the time, you're going to spend a lot of cycles skipping it.

You also might consider using an iterator instead of manipulating your loop variable. Either that or just find items to remove in the loop, then do another loop to remove them all.

Use LINQ. And, aren't we past the 15 char min limit?

Well if we're talking about proper OO, it's probably not a good idea put the responsibility of keeping itemList and whatever RemoveItem does in sync on the caller.

Personally I would use a list which has an OnRemove event you can hook into, and add RemoveItem as the event. This removes the need for the caller to remember to call RemoveItem.

This then makes the code much simpler, allowing you to use something like:

private void RemoveDiscontinuedItems()
{
    itemList.RemoveAll(x => !x.ItemIsOnSite);
}

private void RemovePriceChangedItems()
{
    itemList.RemoveAll(x => !PricingOptionIsValid(x));
}

The code is cleaner and the logic and purpose more obvious.

The performance obviously needn't be a concern unless it becomes a problem, though you must remember to test with extreme values (in this case a large list).

If you find that iterating through the list multiple times is actually a bottleneck then I'd propose something like this:

private bool IsDiscontinuedItem(Item item)
{
    return !item.ItemIsOnSite;
}

private bool IsPriceChangedItem(Item item)
{
    return !PricingOptionIsValid(item);
}

private bool IsInvalidItem(Item item)
{
    return IsDiscontinuedItem(item) ||
           IsPriceChangedItem(item);
}

private void RemoveInvalidItems()
{
    itemList.RemoveAll(IsInvalidItem)
}

Do whatever makes sense, reduces complexity, and is easiest to maintain. Best practices are a guide.

Here's my version:

    private void RemoveDiscontinuedItems()
    {
        RemoveItems(item => item.ItemIsOnSite);
    }

    private void RemovePriceChangedItems()
    {
        RemoveItems(item => PricingOptionIsValid(item));
    }

    private void RemoveAll()
    {
        RemoveItems(item => item.ItemIsOnSite || PricingOptionIsValid(item));
    }

    private void RemoveItems(Predicate<Item> removeIfTruePredicate)
    {
        itemList.RemoveAll(
            item =>
            {
                if(removeIfTruePredicate(item))
                {
                    RemoveItem(item);
                    return true;
                }

                return false;
            });
    }

Note, this is based on your use of a mutable list. I'd be tempted to consider whether a mutable list is really needed. If not, I'd prefer to use a more functional style, and use a linq where clause to create a new list that excludes the items which need removing.

Given your desire for performance though, my guess is that RemoveAll (I'm assuming you're using List<T> ) will be quicker than creation of a new list. Of course you'd really need to check this to be sure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM