First round of optimisation to CardDb

This optimisation removes redundant queries when looking for cards by specificed Art Preference.

getCardsFromSet has been worked-around, avoiding querying for potential candidates already in memory.
A preliminary benchmark/tests is implemented too.
This commit is contained in:
leriomaggio
2021-08-25 18:15:28 +01:00
parent 637aadc188
commit 841c98dc12
2 changed files with 112 additions and 17 deletions

View File

@@ -465,7 +465,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// So now check whether the cards exists in the DB first, // So now check whether the cards exists in the DB first,
// and select pick the card based on current SetPreference policy as a fallback // and select pick the card based on current SetPreference policy as a fallback
Collection<PaperCard> cards = getAllCards(request.cardName); Collection<PaperCard> cards = getAllCards(request.cardName);
if (cards == null) if (cards.isEmpty()) // Never null being this a view in MultiMap
return null; return null;
// Either No Edition has been specified OR as a fallback in case of any error! // Either No Edition has been specified OR as a fallback in case of any error!
// get card using the default card art preference // get card using the default card art preference
@@ -640,6 +640,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override @Override
public boolean apply(PaperCard c) { public boolean apply(PaperCard c) {
CardEdition ed = editions.get(c.getEdition()); CardEdition ed = editions.get(c.getEdition());
if (ed == null) return false;
if (releasedBeforeFlag) if (releasedBeforeFlag)
return ed.getDate().before(releaseDate); return ed.getDate().before(releaseDate);
else else
@@ -648,22 +649,24 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
})); }));
} }
if (cards.size() == 0) // Don't bother continuing! No card has been found!
return null;
/* 2. Retrieve cards based of [Frame]Set Preference /* 2. Retrieve cards based of [Frame]Set Preference
================================================ */ ================================================ */
// Collect the list of all editions found for target card // Collect the list of all editions found for target card
LinkedHashSet<CardEdition> cardEditions = new LinkedHashSet<>(); List<CardEdition> cardEditions = new ArrayList<>();
Map<String, PaperCard> candidatesCard = new HashMap<>();
for (PaperCard card : cards) { for (PaperCard card : cards) {
if (card.getArtIndex() != cr.artIndex)
continue;
String setCode = card.getEdition(); String setCode = card.getEdition();
CardEdition ed = null;
if (setCode.equals(CardEdition.UNKNOWN.getCode())) if (setCode.equals(CardEdition.UNKNOWN.getCode()))
cardEditions.add(CardEdition.UNKNOWN); ed = CardEdition.UNKNOWN;
else { else
CardEdition ed = editions.get(card.getEdition()); ed = editions.get(card.getEdition());
if (ed != null) if (ed != null) {
cardEditions.add(ed); cardEditions.add(ed);
candidatesCard.put(setCode, card);
} }
} }
// Filter Cards Editions based on set preferences // Filter Cards Editions based on set preferences
@@ -681,16 +684,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
If this happens, we won't try to iterate over an empty list. Instead, we will fall back If this happens, we won't try to iterate over an empty list. Instead, we will fall back
to original lists of editions (unfiltered, of course) AND STILL sorted according to chosen art preference. to original lists of editions (unfiltered, of course) AND STILL sorted according to chosen art preference.
*/ */
if (acceptedEditions.size() == 0) if (acceptedEditions.isEmpty())
acceptedEditions.addAll(cardEditions); acceptedEditions.addAll(cardEditions);
Collections.sort(acceptedEditions); // CardEdition correctly sort by (release) date if (acceptedEditions.size() > 1) {
if (artPref.latestFirst) Collections.sort(acceptedEditions); // CardEdition correctly sort by (release) date
Collections.reverse(acceptedEditions); // newest editions first if (artPref.latestFirst)
Collections.reverse(acceptedEditions); // newest editions first
}
PaperCard candidate = null; PaperCard candidate = null;
for (CardEdition ed : acceptedEditions) { for (CardEdition ed : acceptedEditions) {
PaperCard cardFromSet = getCardFromSet(cr.cardName, ed, cr.artIndex, cr.isFoil); PaperCard cardFromSet = candidatesCard.get(ed.getCode()); //getCardFromSet(cr.cardName, ed, cr.artIndex, cr.isFoil);
if (candidate == null && cardFromSet != null) if (candidate == null)
// save the first card found, as the last backup in case no other candidate *with image* will be found // save the first card found, as the last backup in case no other candidate *with image* will be found
candidate = cardFromSet; candidate = cardFromSet;
@@ -699,8 +705,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
break; // we're done here: found card **with Image** break; // we're done here: found card **with Image**
} }
} }
if (candidate == null)
return null;
//If any, we're sure that at least one candidate is always returned nevertheless it has image or not //If any, we're sure that at least one candidate is always returned nevertheless it has image or not
return candidate; // any foil request already handled in getCardFromSet return cr.isFoil ? candidate.getFoiled() : candidate;
} }
@Override @Override

View File

@@ -0,0 +1,86 @@
package forge.card;
import forge.ImageKeys;
import forge.item.PaperCard;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
import static org.testng.Assert.assertNotNull;
public class CardDbPerformanceTests extends CardDbTestCase {
private Set<String> fullDbCardNames = new TreeSet<>();
@Override
@BeforeMethod
public void setup() {
super.setup();
Collection<PaperCard> uniqueCards = this.cardDb.getUniqueCards();
for (PaperCard card : uniqueCards)
this.fullDbCardNames.add(card.getName());
}
@Test
public void testBenchmarkFullDbGetCardLegacyImplementation() {
int nRuns = 100;
long averageTime = 0;
long minTime = 10000; // 10 secs
long maxTime = 0;
for (int r = 1; r <= nRuns; r++) {
long start = System.currentTimeMillis();
for (String name : this.fullDbCardNames) {
PaperCard card = this.legacyCardDb.getCard(name);
assertNotNull(card);
}
long timeRun = System.currentTimeMillis() - start;
averageTime += timeRun;
if (timeRun < minTime)
minTime = timeRun;
if (timeRun > maxTime)
maxTime = timeRun;
}
System.out.println("[LEGACY] Total Time (in sec): " + ((double) averageTime)/ 1000);
System.out.println("[LEGACY] Average Time (in sec): " + ((double) averageTime / nRuns)/ 1000);
System.out.println("[LEGACY] Best Time (in sec): " + ((double) minTime)/ 1000);
System.out.println("[LEGACY] Worst Time (in sec): " + ((double) maxTime)/ 1000);
}
@Test
public void testBenchmarkFullDbGetCardNewDbImplementation() {
int nRuns = 100;
long averageTime = 0;
long minTime = 10000; // 10 secs
long maxTime = 0;
for (int r = 1; r <= nRuns; r++) {
long start = System.currentTimeMillis();
for (String name : this.fullDbCardNames) {
PaperCard card = this.cardDb.getCard(name);
assertNotNull(card);
}
long timeRun = System.currentTimeMillis() - start;
averageTime += timeRun;
if (timeRun < minTime)
minTime = timeRun;
if (timeRun > maxTime)
maxTime = timeRun;
}
System.out.println("[NEW] Total Time (in sec): " + ((double) averageTime)/ 1000);
System.out.println("[NEW] Average Time (in sec): " + ((double) averageTime / nRuns)/ 1000);
System.out.println("[NEW] Best Time (in sec): " + ((double) minTime)/ 1000);
System.out.println("[NEW] Worst Time (in sec): " + ((double) maxTime)/ 1000);
}
@Test
public void testGetCardFullDbNewImplementationToProfile(){
for (String name : this.fullDbCardNames) {
PaperCard card = this.cardDb.getCard(name);
assertNotNull(card);
}
}
}