1

我有一些我想优化的代码。首先,一点也不差,但也许它可以更短或更快,主要是 update_result 方法:

class Round < ActiveRecord::Base
  belongs_to :match
  has_and_belongs_to_many :banned_champions, :class_name => "Champion", :join_table => "banned_champions_rounds"
  belongs_to :clan_blue, :class_name => "Clan", :foreign_key => "clan_blue_id"
  belongs_to :clan_purple, :class_name => "Clan", :foreign_key => "clan_purple_id"
  belongs_to :winner, :class_name => "Clan", :foreign_key => "winner_id"

  after_save {self.update_result}

  def update_result
    match = self.match
    if match.rounds.count > 0
      clan1 = match.rounds.first.clan_blue
      clan2 = match.rounds.first.clan_purple
      results = {clan1=>0, clan2=>0}
      for round in match.rounds
        round.winner == clan1 ? results[clan1] += 1 : results[clan2] += 1
      end
      if results[clan1] > results[clan2] then
        match.winner = clan1; match.looser = clan2
        match.draw_1 = nil; match.draw_2 = nil
      elsif results[clan1] < results[clan2] then
        match.winner = clan2; match.looser = clan1
        match.draw_1 = nil; match.draw_2 = nil
      else
        match.draw_1 = clan1; match.draw_2 = clan2
        match.winner = nil; match.looser = nil
      end
      match.save
    end
  end
end

其次,seeds.rb 完全糟糕且缓慢:

require 'faker'

champions = [{:name=>"Akali"},
{:name=>"Alistar"},
{:name=>"Amumu"},
{:name=>"Anivia"},
{:name=>"Annie"},
{:name=>"Galio"},
{:name=>"Tryndamere"},
{:name=>"Twisted Fate"},
{:name=>"Twitch"},
{:name=>"Udyr"},
{:name=>"Urgot"},
{:name=>"Veigar"}
]

Champion.create(champions)


10.times do |n|
  name = Faker::Company.name
  clan = Clan.create(:name=>name)
  6.times do |n|
    name = Faker::Internet.user_name
    clan.players.create(:name=>name)
  end
end

for clan in Clan.all do
  2.times do
    match = Match.create()
    c = [clan,Clan.first(:offset => rand(Clan.count))]
    3.times do
      round = match.rounds.create
      round.clan_blue = c[0]
      round.clan_purple = c[1]
      round.winner = c[0]
      round.save!
    end
    for item in c
      for p in item.players.limit(5)
        rand_champion = Champion.first(:offset => rand(Champion.count))
        match.participations.create!(:player => p, :champion => rand_champion)
      end
    end
    match.save!
  end
  2.times do
    match = Match.create()
    c = [clan,Clan.first(:offset => rand(Clan.count))]
    3.times do
      round = match.rounds.create
      round.clan_blue = c[0]
      round.clan_purple = c[1]
      round.winner = c[1]
      round.save!
    end
    for item in c
      for p in item.players.limit(5)
        rand_champion = Champion.first(:offset => rand(Champion.count))
        match.participations.create!(:player => p, :champion => rand_champion)
      end
    end
    match.save!
    end
  2.times do
    match = Match.create()
    c = [clan,Clan.first(:offset => rand(Clan.count))]
    2.times do |n|
      round = match.rounds.create
      round.clan_blue = c[0]
      round.clan_purple = c[1]
      round.winner = c[n]
      round.save!
    end
    for item in c
      for p in item.players.limit(5)
        rand_champion = Champion.first(:offset => rand(Champion.count))
        match.participations.create!(:player => p, :champion => rand_champion)
      end
    end
    match.save!
  end
end

有机会优化它们吗?

4

3 回答 3

2

不要低估空格在清理代码可读性方面的价值!

class Round < ActiveRecord::Base
  belongs_to :match

  belongs_to :clan_blue,   :class_name => "Clan", :foreign_key => "clan_blue_id"
  belongs_to :clan_purple, :class_name => "Clan", :foreign_key => "clan_purple_id"
  belongs_to :winner,      :class_name => "Clan", :foreign_key => "winner_id"

  has_and_belongs_to_many :banned_champions, :class_name => "Champion", :join_table => "banned_champions_rounds"

  after_save { match.update_result }
end

class Match < ActiveRecord::Base
  def update_result
    return unless rounds.count > 0

    clan1, clan2 = rounds.first.clan_blue, rounds.first.clan_purple

    clan1_wins = rounds.inject(0) {|total, round| total += round.winner == clan1 ? 1 : 0 }
    clan2_wins = rounds.length - clan1_wins

    self.winner = self.loser = self.draw_1 = self.draw_2 = nil

    if clan1_wins == clan2_wins
      self.draw1, self.draw2 = clan1, clan2
    else
      self.winner = clan1_wins > clan2_wins ? clan1 : clan2
      self.loser  = clan1_wins < clan2_wins ? clan1 : clan2
    end

    save
  end  
end

对于您的种子,如果用于测试,我会用工厂模式替换您的固定装置。但是,如果您要坚持现有的东西,请将整个块包装在一个事务中,它应该会变得更快几个数量级。

于 2011-03-22T17:53:36.080 回答
1

好吧,在您的第一个示例中,您似乎将 Match 行为强制到 Round 类中,这与抽象 OOP 不一致。您的 update_result 方法实际上属于您的 Match 类。一旦你这样做了,我认为代码会自己清理一下。

在您的第二个示例中,很难看出您正在尝试做什么,但它如此缓慢也就不足为奇了。每一次创建和保存都会生成一个单独的数据库调用。乍一看,您的代码会生成一百多个单独的数据库保存。你真的需要所有这些记录吗?你能结合一些保存吗?

除此之外,您可以使用 build 而不是 create 将数据库调用减半,如下所示:

  round = match.rounds.build
  round.clan_blue = c[0]
  round.clan_purple = c[1]
  round.winner = c[0]
  round.save!

如果你想保存一些代码行,你可以用以下语法替换上面的代码:

match.rounds.create(:clan_blue_id => c[0].id, :clan_purple_id => c[1].id, :winner_id => c[0].id)
于 2011-03-19T03:09:34.263 回答
1

在你的种子文件中: c = [clan,Clan.first(:offset => rand(Clan.count))] 这行得通,但看起来你在 Ruby 中选择一个随机数。据我了解,如果您可以用 SQL 而不是 Ruby 做某事,通常会更快。试试这个:

c = [clan,Clan.find(:all, :limit => 1, :order => 'random()')

您不会获得太多收益,因为它每个氏族只运行两次(因此总共 20 倍),但也有类似这两条的线路

# (runs 60x total)
    rand_champion = Champion.first(:offset => rand(Champion.count))

# (runs up to 200x, I think)
    c = [clan,Clan.first(:offset => rand(Clan.count))]

一般来说,您几乎能在您的程序中找到更多需要优化的内容。因此,从重复次数最多的区域(嵌套最深的循环)开始,可以最有效地利用您的时间。我将优化上述 2 行(以及任何其他可能类似的行)作为练习留给您。如果您遇到问题,请在评论中告诉我。

此外,我相信您会在许多回复中得到很多好的建议,因此我强烈建议您设置一个基准测试器,以便您可以衡量差异。确保为您测试的每个版本运行几次,这样您可以获得一个很好的平均值(在后台运行的程序可能会影响您的结果)。

就简单性而言,我认为可读性非常重要。它不会使您的代码运行得更快,但它可以使您的调试更快(而且您的时间很重要!)。给我带来麻烦的几件事是不起眼的变量,例如cp。我有时也会这样做,但是当您在同一范围内有几个这样的变量时,我很快就会想到“那个变量又是什么?”。像temp_clan代替的东西c有很长的路要走。

为了可读性,我也更喜欢.each而不是for. 不过,这完全是个人喜好。

顺便说一句,我喜欢英雄联盟 :)

编辑:(评论不会让我缩进代码)再看一遍,我意识到这个片段可以进一步优化:

  for p in item.players.limit(5)
    rand_champion = Champion.first(:offset => rand(Champion.count))
    match.participations.create!(:player => p, :champion => rand_champion)
  end

改变Champion.first(:offset => rand(Champion.count))

rand_champs = Champion.find(:all, :limit => 5, :order => 'random()')
for p ...
  i = 0
  match.participations.create!(:player => p, :champion => rand_champs(i))
  i++
end

这会将 5 个 SQL 查询减少到 1 个。因为它被称为 60x,这会将您的 SQL 查询从 60 减少到 12。另外,您不会在同一个团队中获得重复的冠军,(或者我猜这可能是如果这是您的意图,则不利)

于 2011-03-24T02:58:45.177 回答